diff --git a/.ci.yaml b/.ci.yaml index 541a156c4a2f..11e7b6356931 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -99,6 +99,17 @@ platform_properties: os: Linux device_type: "SM-A025V" + linux_mokey: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:34v3"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Linux + device_type: "mokey" + mac: properties: contexts: >- @@ -506,6 +517,7 @@ targets: - name: Linux firebase_abstract_method_smoke_test presubmit: false + bringup: true # https://github.com/flutter/flutter/issues/147335 recipe: firebaselab/firebaselab timeout: 60 properties: @@ -538,6 +550,7 @@ targets: - name: Linux firebase_android_embedding_v2_smoke_test recipe: firebaselab/firebaselab + bringup: true # https://github.com/flutter/flutter/issues/147335 timeout: 60 properties: dependencies: >- @@ -569,6 +582,7 @@ targets: - name: Linux firebase_release_smoke_test recipe: firebaselab/firebaselab + bringup: true # https://github.com/flutter/flutter/issues/147335 timeout: 60 properties: dependencies: >- @@ -2796,6 +2810,28 @@ targets: ["devicelab", "android", "linux"] task_name: new_gallery__transition_perf + # Mokey, Impeller + - name: Linux_mokey new_gallery_impeller__transition_perf + recipe: devicelab/devicelab_drone + bringup: true # Device exists only in staging. + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: new_gallery_impeller__transition_perf + + # Mokey, Skia + - name: Linux_mokey new_gallery__transition_perf + recipe: devicelab/devicelab_drone + bringup: true # Device exists only in staging. + 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 @@ -3568,17 +3604,6 @@ targets: ] task_name: flutter_gallery_macos__compile - - name: Mac_benchmark flutter_gallery_macos__start_up - presubmit: false - recipe: devicelab/devicelab_drone - timeout: 60 - properties: - dependencies: >- - [ - {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} - ] - task_name: flutter_gallery_macos__start_up - - name: Mac flutter_packaging_test recipe: packaging/packaging presubmit: false diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d29633e414dc..c6854692b0a7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository == 'flutter/flutter' }} steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: ./bin/flutter test --coverage run: pushd packages/flutter;../../bin/flutter test --coverage -j 1;popd - name: upload coverage diff --git a/.github/workflows/easy-cp.yml b/.github/workflows/easy-cp.yml index 62caf93cc758..a1db8497b575 100644 --- a/.github/workflows/easy-cp.yml +++ b/.github/workflows/easy-cp.yml @@ -30,7 +30,7 @@ jobs: run: | echo "COMMIT_SHA=$(echo ${{ github.event.pull_request.merge_commit_sha }})" >> $GITHUB_ENV - name: Checkout Flutter Repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b with: repository: flutteractionsbot/flutter path: flutter @@ -53,7 +53,7 @@ jobs: # TODO(xilaizhang): remove this step once the template is available on release branches. - name: Get CP Template run: | - curl -o PULL_REQUEST_CP_TEMPLATE.md https://raw.githubusercontent.com/flutter/flutter/master/.github/PR_TEMPLATE/PULL_REQUEST_CP_TEMPLATE.md + curl -o PULL_REQUEST_CP_TEMPLATE.md https://raw.githubusercontent.com/flutter/flutter/main/.github/PR_TEMPLATE/PULL_REQUEST_CP_TEMPLATE.md - name: Create PR on CP success if: ${{ steps.attempt-cp.conclusion == 'success' }} working-directory: ./flutter diff --git a/.github/workflows/minimal.yml b/.github/workflows/minimal.yml index c20c33e816f6..627f0da7f12d 100644 --- a/.github/workflows/minimal.yml +++ b/.github/workflows/minimal.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - name: Checkout Flutter Repo - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b with: repository: flutter/flutter token: ${{ github.token }} @@ -30,7 +30,7 @@ jobs: cd flutter touch empty.json - name: Create Pull Request - uses: peter-evans/create-pull-request@c55203cfde3e5c11a452d352b4393e68b85b4533 + uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e with: path: flutter commit-message: blah diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 61cf485b52c4..ab002b815680 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -23,7 +23,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b with: persist-credentials: false @@ -43,7 +43,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 with: name: SARIF file path: results.sarif @@ -51,6 +51,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a + uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 with: sarif_file: results.sarif diff --git a/AUTHORS b/AUTHORS index 3d9b03421960..42ac6eef590e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -121,3 +121,6 @@ Mahdi Bagheri <1839491@gmail.com> Mok Kah Wai Lucas Saudon Om Phatak +Amir Panahandeh +Kostiantyn Sokolovskyi +Valentin Vignal diff --git a/README.md b/README.md index ddce07b00b94..e19a80f1597a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ open source. * [Install Flutter](https://flutter.dev/get-started/) * [Flutter documentation](https://docs.flutter.dev/) * [Development wiki](https://github.com/flutter/flutter/wiki) -* [Contributing to Flutter](https://github.com/flutter/flutter/blob/master/CONTRIBUTING.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) @@ -121,5 +121,5 @@ Information on how to get started can be found in our [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/master/examples/platform_channel +[interop example]: https://github.com/flutter/flutter/tree/main/examples/platform_channel [Impeller]: https://docs.flutter.dev/perf/impeller diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 8b610d2715f2..2bd0b38bac6e 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -1a13c7d1f40e32decdacc066a922fc95d6f554cb +20fb62ba1455fd1c072eaf76a50090a80163c394 diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 69d06781ea86..21388817ce65 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -78f684ce67719fec6bb763e2ceb12e62a34a2c3e +dd01140f470e02b06d05ffa0213c7b26e89ae4c4 diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index b9b499770cec..beaf93ef950d 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -peYcbx9eguHcbhMP00N4RjvNWuk5s5GR4TrllCDBAmIC +SVcynyah0BO4d5mRMVaNDOAD9JZSX52XnPHGiY-zvnkC diff --git a/dev/a11y_assessments/ios/Runner/AppDelegate.swift b/dev/a11y_assessments/ios/Runner/AppDelegate.swift index 36e03f7fbb9b..58f3ea9b9299 100644 --- a/dev/a11y_assessments/ios/Runner/AppDelegate.swift +++ b/dev/a11y_assessments/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/dev/a11y_assessments/macos/Runner/AppDelegate.swift b/dev/a11y_assessments/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/a11y_assessments/macos/Runner/AppDelegate.swift +++ b/dev/a11y_assessments/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/a11y_assessments/pubspec.yaml b/dev/a11y_assessments/pubspec.yaml index c25a622b0441..18d2c3136bc7 100644 --- a/dev/a11y_assessments/pubspec.yaml +++ b/dev/a11y_assessments/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -32,10 +32,10 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/dev/automated_tests/pubspec.yaml b/dev/automated_tests/pubspec.yaml index 036a223e69bd..a806c6572926 100644 --- a/dev/automated_tests/pubspec.yaml +++ b/dev/automated_tests/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: integration_test: sdk: flutter platform: 3.1.4 - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -28,7 +28,7 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -40,7 +40,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -59,11 +59,11 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,4 +75,4 @@ flutter: assets: - icon/test.png -# PUBSPEC CHECKSUM: 7413 +# PUBSPEC CHECKSUM: 011a diff --git a/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift b/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift index 36e03f7fbb9b..58f3ea9b9299 100644 --- a/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift +++ b/dev/benchmarks/complex_layout/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/dev/benchmarks/complex_layout/macos/Runner/AppDelegate.swift b/dev/benchmarks/complex_layout/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/benchmarks/complex_layout/macos/Runner/AppDelegate.swift +++ b/dev/benchmarks/complex_layout/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index 25ee1292de11..1c227b701935 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,15 +31,15 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 integration_test: sdk: flutter @@ -51,7 +51,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,7 +72,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -86,4 +86,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: c0eb +# PUBSPEC CHECKSUM: 14f2 diff --git a/dev/benchmarks/macrobenchmarks/macos/Runner/AppDelegate.swift b/dev/benchmarks/macrobenchmarks/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/benchmarks/macrobenchmarks/macos/Runner/AppDelegate.swift +++ b/dev/benchmarks/macrobenchmarks/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index 794df558d885..728f0ebaa11b 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -40,13 +40,13 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 + test: 1.25.4 integration_test: sdk: flutter @@ -56,7 +56,7 @@ dev_dependencies: convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,7 +74,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -213,4 +213,4 @@ flutter: fonts: - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf -# PUBSPEC CHECKSUM: c0eb +# PUBSPEC CHECKSUM: 14f2 diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml index 8403629470f6..fdbfc6708600 100644 --- a/dev/benchmarks/microbenchmarks/pubspec.yaml +++ b/dev/benchmarks/microbenchmarks/pubspec.yaml @@ -5,14 +5,14 @@ environment: sdk: '>=3.2.0-0 <4.0.0' dependencies: - meta: 1.12.0 + meta: 1.14.0 flutter: sdk: flutter flutter_test: sdk: flutter stocks: path: ../test_apps/stocks - test: 1.25.2 + test: 1.25.4 flutter_gallery_assets: 1.0.2 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -28,7 +28,7 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,11 +60,11 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -140,4 +140,4 @@ flutter: - packages/flutter_gallery_assets/people/square/stella.png - packages/flutter_gallery_assets/people/square/trevor.png -# PUBSPEC CHECKSUM: 7562 +# PUBSPEC CHECKSUM: 7b69 diff --git a/dev/benchmarks/multiple_flutters/module/pubspec.yaml b/dev/benchmarks/multiple_flutters/module/pubspec.yaml index 6f18a98758d7..5ce44f9debe7 100644 --- a/dev/benchmarks/multiple_flutters/module/pubspec.yaml +++ b/dev/benchmarks/multiple_flutters/module/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 google_fonts: 4.0.4 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -21,7 +21,7 @@ dependencies: http: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_android: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -47,4 +47,4 @@ flutter: androidPackage: com.example.multiple_flutters_module iosBundleIdentifier: com.example.multipleFluttersModule -# PUBSPEC CHECKSUM: 12dd +# PUBSPEC CHECKSUM: e6e1 diff --git a/dev/benchmarks/platform_channels_benchmarks/lib/main.dart b/dev/benchmarks/platform_channels_benchmarks/lib/main.dart index 3876d6e1991b..2b43fea20ad9 100644 --- a/dev/benchmarks/platform_channels_benchmarks/lib/main.dart +++ b/dev/benchmarks/platform_channels_benchmarks/lib/main.dart @@ -11,30 +11,20 @@ import 'package:flutter/services.dart'; import 'package:microbenchmarks/common.dart'; List _makeTestBuffer(int size) { - final List answer = []; - for (int i = 0; i < size; ++i) { - switch (i % 9) { - case 0: - answer.add(1); - case 1: - answer.add(math.pow(2, 65)); - case 2: - answer.add(1234.0); - case 3: - answer.add(null); - case 4: - answer.add([1234]); - case 5: - answer.add({'hello': 1234}); - case 6: - answer.add('this is a test'); - case 7: - answer.add(true); - case 8: - answer.add(Uint8List(64)); - } - } - return answer; + return [ + for (int i = 0; i < size; i++) + switch (i % 9) { + 0 => 1, + 1 => math.pow(2, 65), + 2 => 1234.0, + 3 => null, + 4 => [1234], + 5 => {'hello': 1234}, + 6 => 'this is a test', + 7 => true, + _ => Uint8List(64), + }, + ]; } Future _runBasicStandardSmall( diff --git a/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml b/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml index 8917a9b483a9..5c0179b4b3de 100644 --- a/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml +++ b/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter microbenchmarks: path: ../microbenchmarks - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -30,7 +30,7 @@ dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter_gallery_assets: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,12 +60,12 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test: 1.25.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test: 1.25.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -77,4 +77,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 934e +# PUBSPEC CHECKSUM: d457 diff --git a/dev/benchmarks/platform_views_layout/pubspec.yaml b/dev/benchmarks/platform_views_layout/pubspec.yaml index 8a1cc01245b5..26b139853889 100644 --- a/dev/benchmarks/platform_views_layout/pubspec.yaml +++ b/dev/benchmarks/platform_views_layout/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,9 +32,9 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webview_flutter: 4.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webview_flutter_android: 3.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -44,7 +44,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -54,7 +54,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,7 +75,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -89,4 +89,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: e0a5 +# PUBSPEC CHECKSUM: 4bac diff --git a/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml b/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml index a37865b9ad71..04c0d54ebe60 100644 --- a/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml +++ b/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,15 +31,15 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -49,7 +49,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,7 +70,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -84,4 +84,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: c0eb +# PUBSPEC CHECKSUM: 14f2 diff --git a/dev/benchmarks/test_apps/stocks/pubspec.yaml b/dev/benchmarks/test_apps/stocks/pubspec.yaml index b043a9f4b77c..ed11dded05d1 100644 --- a/dev/benchmarks/test_apps/stocks/pubspec.yaml +++ b/dev/benchmarks/test_apps/stocks/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -42,7 +42,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -66,9 +66,9 @@ dev_dependencies: stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,4 +79,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: d40e +# PUBSPEC CHECKSUM: db15 diff --git a/dev/bots/README.md b/dev/bots/README.md index 491a614e2e54..4d6203edad59 100644 --- a/dev/bots/README.md +++ b/dev/bots/README.md @@ -61,7 +61,7 @@ actions through `recipe_modules`. Searching the builder config in [infra](https: will indicate the recipe used for a test. Recipes are just Python with some limitations on what can be imported. They are -[documented](https://github.com/luci/recipes-py/blob/master/doc/user_guide.md) +[documented](https://github.com/luci/recipes-py/blob/main/doc/user_guide.md) by the [luci/recipes-py GitHub project](https://github.com/luci/recipes-py). The typical cycle for editing a recipe is: diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 4c2cb4779bd2..7f47bf476eb3 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -26,8 +26,6 @@ import 'custom_rules/render_box_intrinsics.dart'; import 'run_command.dart'; import 'utils.dart'; -final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))); -final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); final String flutterPackages = path.join(flutterRoot, 'packages'); final String flutterExamples = path.join(flutterRoot, 'examples'); @@ -109,9 +107,12 @@ Future run(List arguments) async { printProgress('Debug mode instead of checked mode...'); await verifyNoCheckedMode(flutterRoot); - printProgress('Links for creating GitHub issues'); + printProgress('Links for creating GitHub issues...'); await verifyIssueLinks(flutterRoot); + printProgress('Links to repositories...'); + await verifyRepositoryLinks(flutterRoot); + printProgress('Unexpected binaries...'); await verifyNoBinaries(flutterRoot); @@ -471,7 +472,7 @@ Future verifyMaterialFilesAreUpToDateWithTemplateFiles(String workingDirec if (errors.isNotEmpty) { foundError([ ...errors, - '${bold}See: https://github.com/flutter/flutter/blob/master/dev/tools/gen_defaults to update the token template files.$reset', + '${bold}See: https://github.com/flutter/flutter/blob/main/dev/tools/gen_defaults to update the token template files.$reset', ]); } } @@ -790,6 +791,9 @@ Future _verifyNoMissingLicenseForExtension( if (contents.isEmpty) { continue; // let's not go down the /bin/true rabbit hole } + if (path.basename(file.path) == 'Package.swift') { + continue; + } if (!contents.startsWith(RegExp(header + licensePattern))) { errors.add(file.path); } @@ -1220,7 +1224,7 @@ String _bullets(String value) => ' * $value'; Future verifyIssueLinks(String workingDirectory) async { const String issueLinkPrefix = 'https://github.com/flutter/flutter/issues/new'; const Set stops = { '\n', ' ', "'", '"', r'\', ')', '>' }; - assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://", it copy-pastes better + assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://foo", it copy-pastes better const String kGiveTemplates = 'Prefer to provide a link either to $issueLinkPrefix/choose (the list of issue ' 'templates) or to a specific template directly ($issueLinkPrefix?template=...).\n'; @@ -1291,6 +1295,66 @@ Future verifyIssueLinks(String workingDirectory) async { } } +Future verifyRepositoryLinks(String workingDirectory) async { + const Set stops = { '\n', ' ', "'", '"', r'\', ')', '>' }; + assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://foo", it copy-pastes better + + // Repos whose default branch is still 'master' + const Set repoExceptions = { + 'clojure/clojure', + 'dart-lang/test', // TODO(guidezpl): remove when https://github.com/dart-lang/test/issues/2209 is closed + 'eseidelGoogle/bezier_perf', + 'flutter/devtools', // TODO(guidezpl): remove when https://github.com/flutter/devtools/issues/7551 is closed + 'flutter/flutter_gallery_assets', // TODO(guidezpl): remove when subtask in https://github.com/flutter/flutter/issues/121564 is complete + 'flutter/flutter-intellij', // TODO(guidezpl): remove when https://github.com/flutter/flutter-intellij/issues/7342 is closed + 'flutter/platform_tests', // TODO(guidezpl): remove when subtask in https://github.com/flutter/flutter/issues/121564 is complete + 'glfw/glfw', + 'material-components/material-components-android', // TODO(guidezpl): remove when https://github.com/material-components/material-components-android/issues/4144 is closed + 'torvalds/linux', + 'tpn/winsdk-10', + }; + + // See dev/bots/test/analyze-test-input/root/packages/foo/bad_repository_links.dart + // for examples of repository links that are not allowed. + final RegExp pattern = RegExp(r'^(https:\/\/(?:cs\.opensource\.google|github|raw\.githubusercontent|source\.chromium|([a-z0-9\-]+)\.googlesource)\.)'); + + final List problems = []; + final Set suggestions = {}; + final List files = await _allFiles(workingDirectory, null, minimumMatches: 10).toList(); + for (final File file in files) { + final Uint8List bytes = file.readAsBytesSync(); + // We allow invalid UTF-8 here so that binaries don't trip us up. + // There's a separate test in this file that verifies that all text + // files are actually valid UTF-8 (see verifyNoBinaries below). + final String contents = utf8.decode(bytes, allowMalformed: true); + int start = 0; + while ((start = contents.indexOf('https://', start)) >= 0) { // Find all 'https://' links + int end = start + 8; // Length of 'https://' + while (end < contents.length && !stops.contains(contents[end])) { + end += 1; + } + final String url = contents.substring(start, end).replaceAll('\r', ''); + + if (pattern.hasMatch(url) && !repoExceptions.any(url.contains)) { + if (url.contains('master')) { + problems.add('${file.path} contains $url, which uses the banned "master" branch.'); + suggestions.add('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.'); + } + } + start = end; + } + } + assert(problems.isEmpty == suggestions.isEmpty); + if (problems.isNotEmpty) { + foundError([ + ...problems, + ...suggestions, + ]); + } +} + @immutable class Hash256 { const Hash256(this.a, this.b, this.c, this.d); diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index 53e6cfabffed..836be534b31d 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -392,7 +392,6 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/navigator/restorable_route_future.0_test.dart', 'examples/api/test/widgets/navigator/navigator_state.restorable_push.0_test.dart', 'examples/api/test/widgets/focus_manager/focus_node.unfocus.0_test.dart', - 'examples/api/test/widgets/focus_manager/focus_node.0_test.dart', 'examples/api/test/widgets/framework/build_owner.0_test.dart', 'examples/api/test/widgets/framework/error_widget.0_test.dart', 'examples/api/test/widgets/inherited_theme/inherited_theme.0_test.dart', @@ -411,7 +410,6 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/async/future_builder.0_test.dart', 'examples/api/test/widgets/restoration_properties/restorable_value.0_test.dart', 'examples/api/test/widgets/animated_size/animated_size.0_test.dart', - 'examples/api/test/widgets/table/table.0_test.dart', 'examples/api/test/widgets/animated_switcher/animated_switcher.0_test.dart', 'examples/api/test/widgets/transitions/relative_positioned_transition.0_test.dart', 'examples/api/test/widgets/transitions/positioned_transition.0_test.dart', @@ -437,7 +435,6 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/shortcuts/shortcuts.0_test.dart', 'examples/api/test/widgets/shortcuts/single_activator.single_activator.0_test.dart', 'examples/api/test/widgets/shortcuts/shortcuts.1_test.dart', - 'examples/api/test/widgets/shortcuts/character_activator.0_test.dart', 'examples/api/test/widgets/shortcuts/callback_shortcuts.0_test.dart', 'examples/api/test/widgets/page_storage/page_storage.0_test.dart', 'examples/api/test/widgets/scrollbar/raw_scrollbar.1_test.dart', @@ -453,8 +450,6 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/interactive_viewer/interactive_viewer.transformation_controller.0_test.dart', 'examples/api/test/widgets/interactive_viewer/interactive_viewer.0_test.dart', 'examples/api/test/widgets/notification_listener/notification.0_test.dart', - 'examples/api/test/widgets/gesture_detector/gesture_detector.1_test.dart', - 'examples/api/test/widgets/gesture_detector/gesture_detector.0_test.dart', 'examples/api/test/widgets/editable_text/text_editing_controller.0_test.dart', 'examples/api/test/widgets/editable_text/editable_text.on_changed.0_test.dart', 'examples/api/test/widgets/undo_history/undo_history_controller.0_test.dart', @@ -463,7 +458,6 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/tween_animation_builder/tween_animation_builder.0_test.dart', 'examples/api/test/widgets/single_child_scroll_view/single_child_scroll_view.1_test.dart', 'examples/api/test/widgets/single_child_scroll_view/single_child_scroll_view.0_test.dart', - 'examples/api/test/widgets/overflow_bar/overflow_bar.0_test.dart', 'examples/api/test/widgets/restoration/restoration_mixin.0_test.dart', 'examples/api/test/widgets/actions/actions.0_test.dart', 'examples/api/test/widgets/actions/action_listener.0_test.dart', @@ -474,13 +468,7 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/focus_scope/focus.1_test.dart', 'examples/api/test/widgets/focus_scope/focus_scope.0_test.dart', 'examples/api/test/widgets/implicit_animations/animated_fractionally_sized_box.0_test.dart', - 'examples/api/test/widgets/implicit_animations/animated_align.0_test.dart', - 'examples/api/test/widgets/implicit_animations/animated_positioned.0_test.dart', - 'examples/api/test/widgets/implicit_animations/animated_padding.0_test.dart', - 'examples/api/test/widgets/implicit_animations/sliver_animated_opacity.0_test.dart', - 'examples/api/test/widgets/dismissible/dismissible.0_test.dart', 'examples/api/test/widgets/scroll_view/custom_scroll_view.1_test.dart', - 'examples/api/test/widgets/preferred_size/preferred_size.0_test.dart', 'examples/api/test/widgets/inherited_notifier/inherited_notifier.0_test.dart', 'examples/api/test/animation/curves/curve2_d.0_test.dart', 'examples/api/test/gestures/pointer_signal_resolver/pointer_signal_resolver.0_test.dart', diff --git a/dev/bots/codelabs_build_test.sh b/dev/bots/codelabs_build_test.sh index ef594a9d7284..e5f70c1f6917 100755 --- a/dev/bots/codelabs_build_test.sh +++ b/dev/bots/codelabs_build_test.sh @@ -29,7 +29,7 @@ if [ ${PIPESTATUS[0]} -eq 0 ] || is_expected_failure "$log_file"; then rm "$log_file" else all_builds_ok=0 - echo "View https://github.com/flutter/flutter/blob/master/dev/bots/README.md for steps to resolve this failed build test." >> ${log_file} + echo "View https://github.com/flutter/flutter/blob/main/dev/bots/README.md for steps to resolve this failed build test." >> ${log_file} echo echo "Log left in $log_file." echo diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index f3bf203ba3b0..dfb864d206f5 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -13,11 +13,11 @@ dependencies: flutter_devicelab: path: ../devicelab http_parser: 4.0.2 - meta: 1.12.0 + meta: 1.14.0 path: 1.9.0 platform: 3.1.4 process: 5.0.2 - test: 1.25.2 + test: 1.25.4 _discoveryapis_commons: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -29,7 +29,7 @@ dependencies: collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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" gcloud: 0.8.12 # 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" googleapis: 12.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,9 +62,9 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -73,6 +73,6 @@ dependencies: yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test_api: 0.7.0 + test_api: 0.7.1 -# PUBSPEC CHECKSUM: b402 +# PUBSPEC CHECKSUM: df09 diff --git a/dev/bots/suite_runners/run_add_to_app_life_cycle_tests.dart b/dev/bots/suite_runners/run_add_to_app_life_cycle_tests.dart index 7e9ff835dbce..a97db0c75f16 100644 --- a/dev/bots/suite_runners/run_add_to_app_life_cycle_tests.dart +++ b/dev/bots/suite_runners/run_add_to_app_life_cycle_tests.dart @@ -9,7 +9,7 @@ import 'package:path/path.dart' as path; import '../run_command.dart'; import '../utils.dart'; -Future addToAppLifeCycleRunner(String flutterRoot) async { +Future addToAppLifeCycleRunner() async { if (Platform.isMacOS) { printProgress('${green}Running add-to-app life cycle iOS integration tests$reset...'); final String addToAppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app_life_cycle'); diff --git a/dev/bots/suite_runners/run_analyze_tests.dart b/dev/bots/suite_runners/run_analyze_tests.dart index 0a6a226d888f..77ffc2f66fcd 100644 --- a/dev/bots/suite_runners/run_analyze_tests.dart +++ b/dev/bots/suite_runners/run_analyze_tests.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as path; import '../run_command.dart'; import '../utils.dart'; -Future analyzeRunner(String flutterRoot) async { +Future analyzeRunner() async { printProgress('${green}Running analysis testing$reset'); await runCommand( 'dart', diff --git a/dev/bots/suite_runners/run_android_preview_integration_tool_tests.dart b/dev/bots/suite_runners/run_android_preview_integration_tool_tests.dart new file mode 100644 index 000000000000..ff719d8ecfd0 --- /dev/null +++ b/dev/bots/suite_runners/run_android_preview_integration_tool_tests.dart @@ -0,0 +1,25 @@ +// 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 '../utils.dart'; + +Future androidPreviewIntegrationToolTestsRunner() async { + final String toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools'); + + final List allTests = Directory(path.join(toolsPath, 'test', 'android_preview_integration.shard')) + .listSync(recursive: true).whereType() + .map((FileSystemEntity entry) => path.relative(entry.path, from: toolsPath)) + .where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList(); + + await runDartTest( + toolsPath, + forceSingleCore: true, + testPaths: selectIndexOfTotalSubshard(allTests), + collectMetrics: true, + ); +} diff --git a/dev/bots/suite_runners/run_customer_testing_tests.dart b/dev/bots/suite_runners/run_customer_testing_tests.dart index d2e8e45e04ab..adb3ff36c1c7 100644 --- a/dev/bots/suite_runners/run_customer_testing_tests.dart +++ b/dev/bots/suite_runners/run_customer_testing_tests.dart @@ -9,7 +9,7 @@ import 'package:path/path.dart' as path; import '../run_command.dart'; import '../utils.dart'; -Future customerTestingRunner(String flutterRoot) async { +Future customerTestingRunner() async { printProgress('${green}Running customer testing$reset'); await runCommand( 'git', diff --git a/dev/bots/suite_runners/run_docs_tests.dart b/dev/bots/suite_runners/run_docs_tests.dart index 729ca0b61487..dec0a845271f 100644 --- a/dev/bots/suite_runners/run_docs_tests.dart +++ b/dev/bots/suite_runners/run_docs_tests.dart @@ -5,7 +5,7 @@ import '../run_command.dart'; import '../utils.dart'; -Future docsRunner(String flutterRoot) async { +Future docsRunner() async { printProgress('${green}Running flutter doc tests$reset'); await runCommand( './dev/bots/docs.sh', diff --git a/dev/bots/suite_runners/run_flutter_packages_tests.dart b/dev/bots/suite_runners/run_flutter_packages_tests.dart index 91e7d4cfed9c..e5605da8044a 100644 --- a/dev/bots/suite_runners/run_flutter_packages_tests.dart +++ b/dev/bots/suite_runners/run_flutter_packages_tests.dart @@ -9,11 +9,10 @@ import 'package:file/local.dart'; import 'package:path/path.dart' as path; import '../run_command.dart'; -import '../test.dart'; import '../utils.dart'; /// Executes the test suite for the flutter/packages repo. -Future flutterPackagesRunner(String flutterRoot) async { +Future flutterPackagesRunner() async { Future runAnalyze() async { printProgress('${green}Running analysis for flutter/packages$reset'); diff --git a/dev/bots/suite_runners/run_framework_coverage_tests.dart b/dev/bots/suite_runners/run_framework_coverage_tests.dart new file mode 100644 index 000000000000..d7413b66f263 --- /dev/null +++ b/dev/bots/suite_runners/run_framework_coverage_tests.dart @@ -0,0 +1,33 @@ +// 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 File; + +import 'package:path/path.dart' as path; + +import '../utils.dart'; + +Future frameworkCoverageRunner() async { + final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info')); + if (!coverageFile.existsSync()) { + foundError([ + '${red}Coverage file not found.$reset', + 'Expected to find: $cyan${coverageFile.absolute.path}$reset', + 'This file is normally obtained by running `${green}flutter update-packages$reset`.', + ]); + return; + } + coverageFile.deleteSync(); + await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'), + options: const ['--coverage'], + ); + if (!coverageFile.existsSync()) { + foundError([ + '${red}Coverage file not found.$reset', + 'Expected to find: $cyan${coverageFile.absolute.path}$reset', + 'This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.', + ]); + return; + } +} diff --git a/dev/bots/suite_runners/run_framework_tests.dart b/dev/bots/suite_runners/run_framework_tests.dart new file mode 100644 index 000000000000..d14ecd1d5d32 --- /dev/null +++ b/dev/bots/suite_runners/run_framework_tests.dart @@ -0,0 +1,307 @@ +// 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 Directory, File, FileSystemEntity, Platform, Process; +import 'dart:typed_data'; + +import 'package:archive/archive.dart'; +import 'package:path/path.dart' as path; + +import '../run_command.dart'; +import '../utils.dart'; +import 'run_test_harness_tests.dart'; + +Future frameworkTestsRunner() async { + final List trackWidgetCreationAlternatives = ['--track-widget-creation', '--no-track-widget-creation']; + + Future runWidgets() async { + printProgress('${green}Running packages/flutter tests $reset for ${cyan}test/widgets/$reset'); + for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter'), + options: [trackWidgetCreationOption], + tests: [ path.join('test', 'widgets') + path.separator ], + ); + } + // Try compiling code outside of the packages/flutter directory with and without --track-widget-creation + for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { + await runFlutterTest( + path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery'), + options: [trackWidgetCreationOption], + fatalWarnings: false, // until we've migrated video_player + ); + } + // Run release mode tests (see packages/flutter/test_release/README.md) + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter'), + options: ['--dart-define=dart.vm.product=true'], + tests: ['test_release${path.separator}'], + ); + // Run profile mode tests (see packages/flutter/test_profile/README.md) + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter'), + options: ['--dart-define=dart.vm.product=false', '--dart-define=dart.vm.profile=true'], + tests: ['test_profile${path.separator}'], + ); + } + + Future runImpeller() async { + printProgress('${green}Running packages/flutter tests $reset in Impeller$reset'); + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter'), + options: ['--enable-impeller'], + ); + } + + + Future runLibraries() async { + final List tests = Directory(path.join(flutterRoot, 'packages', 'flutter', 'test')) + .listSync(followLinks: false) + .whereType() + .where((Directory dir) => !dir.path.endsWith('widgets')) + .map((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator) + .toList(); + printProgress('${green}Running packages/flutter tests$reset for $cyan${tests.join(", ")}$reset'); + for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter'), + options: [trackWidgetCreationOption], + tests: tests, + ); + } + } + + Future runExampleTests() async { + await runCommand( + flutter, + ['config', '--enable-${Platform.operatingSystem}-desktop'], + workingDirectory: flutterRoot, + ); + await runCommand( + dart, + [path.join(flutterRoot, 'dev', 'tools', 'examples_smoke_test.dart')], + workingDirectory: path.join(flutterRoot, 'examples', 'api'), + ); + for (final FileSystemEntity entity in Directory(path.join(flutterRoot, 'examples')).listSync()) { + if (entity is! Directory || !Directory(path.join(entity.path, 'test')).existsSync()) { + continue; + } + await runFlutterTest(entity.path); + } + } + + Future runTracingTests() async { + final String tracingDirectory = path.join(flutterRoot, 'dev', 'tracing_tests'); + + // run the tests for debug mode + await runFlutterTest(tracingDirectory, options: ['--enable-vmservice']); + + Future> verifyTracingAppBuild({ + required String modeArgument, + required String sourceFile, + required Set allowed, + required Set disallowed, + }) async { + try { + await runCommand( + flutter, + [ + 'build', 'appbundle', '--$modeArgument', path.join('lib', sourceFile), + ], + workingDirectory: tracingDirectory, + ); + final Archive archive = ZipDecoder().decodeBytes(File(path.join(tracingDirectory, 'build', 'app', 'outputs', 'bundle', modeArgument, 'app-$modeArgument.aab')).readAsBytesSync()); + final ArchiveFile libapp = archive.findFile('base/lib/arm64-v8a/libapp.so')!; + final Uint8List libappBytes = libapp.content as Uint8List; // bytes decompressed here + final String libappStrings = utf8.decode(libappBytes, allowMalformed: true); + await runCommand(flutter, ['clean'], workingDirectory: tracingDirectory); + final List results = []; + for (final String pattern in allowed) { + if (!libappStrings.contains(pattern)) { + results.add('When building with --$modeArgument, expected to find "$pattern" in libapp.so but could not find it.'); + } + } + for (final String pattern in disallowed) { + if (libappStrings.contains(pattern)) { + results.add('When building with --$modeArgument, expected to not find "$pattern" in libapp.so but did find it.'); + } + } + return results; + } catch (error, stackTrace) { + return [ + error.toString(), + ...stackTrace.toString().trimRight().split('\n'), + ]; + } + } + + final List results = []; + results.addAll(await verifyTracingAppBuild( + modeArgument: 'profile', + sourceFile: 'control.dart', // this is the control, the other two below are the actual test + allowed: { + 'TIMELINE ARGUMENTS TEST CONTROL FILE', + 'toTimelineArguments used in non-debug build', // we call toTimelineArguments directly to check the message does exist + }, + disallowed: { + 'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE', + }, + )); + results.addAll(await verifyTracingAppBuild( + modeArgument: 'profile', + sourceFile: 'test.dart', + allowed: { + 'BUILT IN PROFILE MODE', 'RenderTest.performResize called', // controls + 'BUILD', 'LAYOUT', 'PAINT', // we output these to the timeline in profile builds + // (LAYOUT and PAINT also exist because of NEEDS-LAYOUT and NEEDS-PAINT in RenderObject.toStringShort) + }, + disallowed: { + 'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE', + 'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only + 'toTimelineArguments used in non-debug build', // entire function should get dropped by tree shaker + }, + )); + results.addAll(await verifyTracingAppBuild( + modeArgument: 'release', + sourceFile: 'test.dart', + allowed: { + 'BUILT IN RELEASE MODE', 'RenderTest.performResize called', // controls + }, + disallowed: { + 'BUILT IN DEBUG MODE', 'BUILT IN PROFILE MODE', + 'BUILD', 'LAYOUT', 'PAINT', // these are only used in Timeline.startSync calls that should not appear in release builds + 'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only + 'toTimelineArguments used in non-debug build', // not included in release builds + }, + )); + if (results.isNotEmpty) { + foundError(results); + } + } + + Future runFixTests(String package) async { + final List args = [ + 'fix', + '--compare-to-golden', + ]; + await runCommand( + dart, + args, + workingDirectory: path.join(flutterRoot, 'packages', package, 'test_fixes'), + ); + } + + Future runPrivateTests() async { + final List args = [ + 'run', + 'bin/test_private.dart', + ]; + final Map environment = { + 'FLUTTER_ROOT': flutterRoot, + if (Directory(pubCache).existsSync()) + 'PUB_CACHE': pubCache, + }; + adjustEnvironmentToEnableFlutterAsserts(environment); + await runCommand( + dart, + args, + workingDirectory: path.join(flutterRoot, 'packages', 'flutter', 'test_private'), + environment: environment, + ); + } + + // Tests that take longer than average to run. This is usually because they + // need to compile something large or make use of the analyzer for the test. + // These tests need to be platform agnostic as they are only run on a linux + // machine to save on execution time and cost. + Future runSlow() async { + printProgress('${green}Running slow package tests$reset for directories other than packages/flutter'); + await runTracingTests(); + await runFixTests('flutter'); + await runFixTests('flutter_test'); + await runFixTests('integration_test'); + await runFixTests('flutter_driver'); + await runPrivateTests(); + } + + Future runMisc() async { + printProgress('${green}Running package tests$reset for directories other than packages/flutter'); + await testHarnessTestsRunner(); + await runExampleTests(); + await runFlutterTest( + path.join(flutterRoot, 'dev', 'a11y_assessments'), + tests: [ 'test' ], + ); + await runDartTest(path.join(flutterRoot, 'dev', 'bots')); + await runDartTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 + await runDartTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); + // TODO(gspencergoog): Remove the exception for fatalWarnings once https://github.com/flutter/flutter/issues/113782 has landed. + await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false); + await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'ui')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'tools')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_defaults')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks')); + await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: [path.join('test', 'src', 'real_tests')]); + await runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: [ + '--enable-vmservice', + // Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard. + '--exclude-tags=web', + ]); + // Run java unit tests for integration_test + // + // Generate Gradle wrapper if it doesn't exist. + Process.runSync( + flutter, + ['build', 'apk', '--config-only'], + workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'), + ); + await runCommand( + path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android', 'gradlew$bat'), + [ + ':integration_test:testDebugUnitTest', + '--tests', + 'dev.flutter.plugins.integration_test.FlutterDeviceScreenshotTest', + ], + workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'), + ); + await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens')); + await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations')); + await runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test')); + await runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol')); + await runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')); + const String httpClientWarning = + 'Warning: At least one test in this suite creates an HttpClient. When running a test suite that uses\n' + 'TestWidgetsFlutterBinding, all HTTP requests will return status code 400, and no network request\n' + 'will actually be made. Any test expecting a real network connection and status code will fail.\n' + 'To test code that needs an HttpClient, provide your own HttpClient implementation to the code under\n' + 'test, so that your test can consistently provide a testable response to the code under test.'; + await runFlutterTest( + path.join(flutterRoot, 'packages', 'flutter_test'), + script: path.join('test', 'bindings_test_failure.dart'), + expectFailure: true, + printOutput: false, + outputChecker: (CommandResult result) { + final Iterable matches = httpClientWarning.allMatches(result.flattenedStdout!); + if (matches.isEmpty || matches.length > 1) { + return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n\n' + 'stdout:\n${result.flattenedStdout}\n\n' + 'stderr:\n${result.flattenedStderr}'; + } + return null; + }, + ); + } + + await selectSubshard({ + 'widgets': runWidgets, + 'libraries': runLibraries, + 'slow': runSlow, + 'misc': runMisc, + 'impeller': runImpeller, + }); +} diff --git a/dev/bots/suite_runners/run_fuchsia_precache.dart b/dev/bots/suite_runners/run_fuchsia_precache.dart index 2d55f6504e29..364cf7c50bfd 100644 --- a/dev/bots/suite_runners/run_fuchsia_precache.dart +++ b/dev/bots/suite_runners/run_fuchsia_precache.dart @@ -6,7 +6,7 @@ import '../run_command.dart'; import '../utils.dart'; // Runs flutter_precache. -Future fuchsiaPrecacheRunner(String flutterRoot) async { +Future fuchsiaPrecacheRunner() async { printProgress('${green}Running flutter precache tests$reset'); await runCommand( 'flutter', diff --git a/dev/bots/suite_runners/run_realm_checker_tests.dart b/dev/bots/suite_runners/run_realm_checker_tests.dart index 398054fdd74e..f4f78e7aab68 100644 --- a/dev/bots/suite_runners/run_realm_checker_tests.dart +++ b/dev/bots/suite_runners/run_realm_checker_tests.dart @@ -8,7 +8,7 @@ import 'package:path/path.dart' as path; import '../utils.dart'; -Future realmCheckerTestRunner(String flutterRoot) async { +Future realmCheckerTestRunner() async { final String engineRealmFile = path.join(flutterRoot, 'bin', 'internal', 'engine.realm'); final String engineRealm = File(engineRealmFile).readAsStringSync().trim(); diff --git a/dev/bots/suite_runners/run_test_harness_tests.dart b/dev/bots/suite_runners/run_test_harness_tests.dart new file mode 100644 index 000000000000..9ebc2d006c9b --- /dev/null +++ b/dev/bots/suite_runners/run_test_harness_tests.dart @@ -0,0 +1,170 @@ +// 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 File, Platform; + +import 'package:path/path.dart' as path; + +import '../run_command.dart'; +import '../utils.dart'; + +String get platformFolderName { + if (Platform.isWindows) { + return 'windows-x64'; + } + if (Platform.isMacOS) { + return 'darwin-x64'; + } + if (Platform.isLinux) { + return 'linux-x64'; + } + throw UnsupportedError('The platform ${Platform.operatingSystem} is not supported by this script.'); +} + +Future testHarnessTestsRunner() async { + + printProgress('${green}Running test harness tests...$reset'); + + await _validateEngineHash(); + + // Verify that the tests actually return failure on failure and success on + // success. + final String automatedTests = path.join(flutterRoot, 'dev', 'automated_tests'); + + // We want to run these tests in parallel, because they each take some time + // to run (e.g. compiling), so we don't want to run them in series, especially + // on 20-core machines. However, we have a race condition, so for now... + // Race condition issue: https://github.com/flutter/flutter/issues/90026 + final List tests = [ + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'pass_test.dart'), + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'fail_test.dart'), + expectFailure: true, + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'), + expectFailure: true, + printOutput: false, + outputChecker: (CommandResult result) { + return result.flattenedStdout!.contains('failingPendingTimerTest') + ? null + : 'Failed to find the stack trace for the pending Timer.\n\n' + 'stdout:\n${result.flattenedStdout}\n\n' + 'stderr:\n${result.flattenedStderr}'; + }, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'fail_test_on_exception_after_test.dart'), + expectFailure: true, + printOutput: false, + outputChecker: (CommandResult result) { + const String expectedError = '══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════\n' + 'The following StateError was thrown running a test (but after the test had completed):\n' + 'Bad state: Exception thrown after test completed.'; + if (result.flattenedStdout!.contains(expectedError)) { + return null; + } + return 'Failed to find expected output on stdout.\n\n' + 'Expected output:\n$expectedError\n\n' + 'Actual stdout:\n${result.flattenedStdout}\n\n' + 'Actual stderr:\n${result.flattenedStderr}'; + }, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'crash1_test.dart'), + expectFailure: true, + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'crash2_test.dart'), + expectFailure: true, + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'syntax_error_test.broken_dart'), + expectFailure: true, + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'missing_import_test.broken_dart'), + expectFailure: true, + printOutput: false, + ), + () => runFlutterTest( + automatedTests, + script: path.join('test_smoke_test', 'disallow_error_reporter_modification_test.dart'), + expectFailure: true, + printOutput: false, + ), + ]; + + List testsToRun; + + // Run all tests unless sharding is explicitly specified. + final String? shardName = Platform.environment[kShardKey]; + if (shardName == kTestHarnessShardName) { + testsToRun = selectIndexOfTotalSubshard(tests); + } else { + testsToRun = tests; + } + for (final ShardRunner test in testsToRun) { + await test(); + } + + // Verify that we correctly generated the version file. + final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); + if (versionError != null) { + foundError([versionError]); + } +} + +/// Verify the Flutter Engine is the revision in +/// bin/cache/internal/engine.version. +Future _validateEngineHash() async { + final String flutterTester = path.join(flutterRoot, 'bin', 'cache', 'artifacts', 'engine', platformFolderName, 'flutter_tester$exe'); + + if (runningInDartHHHBot) { + // The Dart HHH bots intentionally modify the local artifact cache + // and then use this script to run Flutter's test suites. + // Because the artifacts have been changed, this particular test will return + // a false positive and should be skipped. + print('${yellow}Skipping Flutter Engine Version Validation for swarming bot $luciBotId.'); + return; + } + final String expectedVersion = File(engineVersionFile).readAsStringSync().trim(); + final CommandResult result = await runCommand(flutterTester, ['--help'], outputMode: OutputMode.capture); + if (result.flattenedStdout!.isNotEmpty) { + foundError([ + '${red}The stdout of `$flutterTester --help` was not empty:$reset', + ...result.flattenedStdout!.split('\n').map((String line) => ' $gray┆$reset $line'), + ]); + } + final String actualVersion; + try { + actualVersion = result.flattenedStderr!.split('\n').firstWhere((final String line) { + return line.startsWith('Flutter Engine Version:'); + }); + } on StateError { + foundError([ + '${red}Could not find "Flutter Engine Version:" line in `${path.basename(flutterTester)} --help` stderr output:$reset', + ...result.flattenedStderr!.split('\n').map((String line) => ' $gray┆$reset $line'), + ]); + return; + } + if (!actualVersion.contains(expectedVersion)) { + foundError(['${red}Expected "Flutter Engine Version: $expectedVersion", but found "$actualVersion".$reset']); + } +} diff --git a/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart b/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart index 91c96f1b33df..df49e68a0c03 100644 --- a/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart +++ b/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart @@ -12,7 +12,7 @@ import 'package:process/process.dart'; import '../run_command.dart'; import '../utils.dart'; -Future verifyCodesignedTestRunner(String flutterRoot) async { +Future verifyCodesignedTestRunner() async { printProgress('${green}Running binaries codesign verification$reset'); await runCommand( 'flutter', @@ -139,28 +139,21 @@ Future verifyExist( String flutterRoot, {@visibleForTesting ProcessManager processManager = const LocalProcessManager() }) async { - final Set foundFiles = {}; - final String cacheDirectory = path.join(flutterRoot, 'bin', 'cache'); - - for (final String binaryPath - in await findBinaryPaths(cacheDirectory, processManager: processManager)) { - if (binariesWithEntitlements(flutterRoot).contains(binaryPath)) { - foundFiles.add(binaryPath); - } else if (binariesWithoutEntitlements(flutterRoot).contains(binaryPath)) { - foundFiles.add(binaryPath); - } else { - throw Exception( - 'Found unexpected binary in cache: $binaryPath'); - } - } - + final List binaryPaths = await findBinaryPaths( + path.join(flutterRoot, 'bin', 'cache'), + processManager: processManager, + ); final List allExpectedFiles = binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot); + final Set foundFiles = { + for (final String binaryPath in binaryPaths) + if (allExpectedFiles.contains(binaryPath)) binaryPath + else throw Exception('Found unexpected binary in cache: $binaryPath'), + }; + if (foundFiles.length < allExpectedFiles.length) { - final List unfoundFiles = allExpectedFiles - .where( - (String file) => !foundFiles.contains(file), - ) - .toList(); + final List unfoundFiles = [ + for (final String file in allExpectedFiles) if (!foundFiles.contains(file)) file, + ]; print( 'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n' 'If this commit is removing binaries from the cache, this test should be fixed by\n' diff --git a/dev/bots/suite_runners/run_web_long_running_tests.dart b/dev/bots/suite_runners/run_web_long_running_tests.dart deleted file mode 100644 index 5ae5d0c2754b..000000000000 --- a/dev/bots/suite_runners/run_web_long_running_tests.dart +++ /dev/null @@ -1,586 +0,0 @@ -// 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 File, HttpClient, HttpClientRequest, HttpClientResponse, Process, RawSocket, SocketDirection, SocketException; -import 'dart:math' as math; -import 'package:path/path.dart' as path; - -import '../browser.dart'; -import '../run_command.dart'; -import '../service_worker_test.dart'; -import '../test.dart'; -import '../utils.dart'; - -const List _kAllBuildModes = ['debug', 'profile', 'release']; - -/// Coarse-grained integration tests running on the Web. -Future webLongRunningTestsRunner(String flutterRoot) async { - - final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engine.version'); - final String engineRealmFile = path.join(flutterRoot, 'bin', 'internal', 'engine.realm'); - final String engineVersion = File(engineVersionFile).readAsStringSync().trim(); - final String engineRealm = File(engineRealmFile).readAsStringSync().trim(); - if (engineRealm.isNotEmpty) { - return; - } - final List tests = [ - for (final String buildMode in _kAllBuildModes) ...[ - () => _runFlutterDriverWebTest( - testAppDirectory: path.join('packages', 'integration_test', 'example'), - target: path.join('test_driver', 'failure.dart'), - buildMode: buildMode, - renderer: 'canvaskit', - wasm: false, - // This test intentionally fails and prints stack traces in the browser - // logs. To avoid confusion, silence browser output. - silenceBrowserOutput: true, - ), - () => _runFlutterDriverWebTest( - testAppDirectory: path.join('packages', 'integration_test', 'example'), - target: path.join('integration_test', 'example_test.dart'), - driver: path.join('test_driver', 'integration_test.dart'), - buildMode: buildMode, - renderer: 'canvaskit', - wasm: false, - expectWriteResponseFile: true, - expectResponseFileContent: 'null', - ), - () => _runFlutterDriverWebTest( - testAppDirectory: path.join('packages', 'integration_test', 'example'), - target: path.join('integration_test', 'example_test.dart'), - driver: path.join('test_driver', 'integration_test.dart'), - buildMode: buildMode, - renderer: 'skwasm', - wasm: true, - expectWriteResponseFile: true, - expectResponseFileContent: 'null', - ), - () => _runFlutterDriverWebTest( - testAppDirectory: path.join('packages', 'integration_test', 'example'), - target: path.join('integration_test', 'extended_test.dart'), - driver: path.join('test_driver', 'extended_integration_test.dart'), - buildMode: buildMode, - renderer: 'canvaskit', - wasm: false, - expectWriteResponseFile: true, - expectResponseFileContent: ''' -{ - "screenshots": [ - { - "screenshotName": "platform_name", - "bytes": [] - }, - { - "screenshotName": "platform_name_2", - "bytes": [] - } - ] -}''', - ), - ], - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - () => _runWebE2eTest('platform_messages_integration', buildMode: 'debug', renderer: 'canvaskit'), - () => _runWebE2eTest('platform_messages_integration', buildMode: 'profile', renderer: 'html'), - () => _runWebE2eTest('platform_messages_integration', buildMode: 'release', renderer: 'html'), - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'debug', renderer: 'html'), - () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'profile', renderer: 'canvaskit'), - () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'release', renderer: 'html'), - - // This test is only known to work in debug mode. - () => _runWebE2eTest('scroll_wheel_integration', buildMode: 'debug', renderer: 'html'), - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - // These tests have been extremely flaky, so we are temporarily disabling them until we figure out how to make them more robust. - // See https://github.com/flutter/flutter/issues/143834 - // () => _runWebE2eTest('text_editing_integration', buildMode: 'debug', renderer: 'canvaskit'), - // () => _runWebE2eTest('text_editing_integration', buildMode: 'profile', renderer: 'html'), - // () => _runWebE2eTest('text_editing_integration', buildMode: 'release', renderer: 'html'), - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - () => _runWebE2eTest('url_strategy_integration', buildMode: 'debug', renderer: 'html'), - () => _runWebE2eTest('url_strategy_integration', buildMode: 'profile', renderer: 'canvaskit'), - () => _runWebE2eTest('url_strategy_integration', buildMode: 'release', renderer: 'html'), - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - () => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'debug', renderer: 'auto'), - () => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'profile', renderer: 'canvaskit'), - () => _runWebE2eTest('capabilities_integration_html', buildMode: 'release', renderer: 'html'), - () => _runWebE2eTest('capabilities_integration_skwasm', buildMode: 'release', renderer: 'skwasm', wasm: true), - - // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. - // CacheWidth and CacheHeight are only currently supported in CanvasKit mode, so we don't run the test in HTML mode. - () => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'debug', renderer: 'auto'), - () => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'profile', renderer: 'canvaskit'), - - () => _runWebTreeshakeTest(), - - () => _runFlutterDriverWebTest( - testAppDirectory: path.join(flutterRoot, 'examples', 'hello_world'), - target: 'test_driver/smoke_web_engine.dart', - buildMode: 'profile', - renderer: 'auto', - wasm: false, - ), - () => _runGalleryE2eWebTest('debug'), - () => _runGalleryE2eWebTest('debug', canvasKit: true), - () => _runGalleryE2eWebTest('profile'), - () => _runGalleryE2eWebTest('profile', canvasKit: true), - () => _runGalleryE2eWebTest('release'), - () => _runGalleryE2eWebTest('release', canvasKit: true), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn), - () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsNonceOn), - () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs), - () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs), - () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort), - () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent), - () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn), - () => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true), - () => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true), - () => runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: true), - () => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'), - () => _runWebStackTraceTest('release', 'lib/stack_trace.dart'), - () => _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'), - () => _runWebStackTraceTest('release', 'lib/framework_stack_trace.dart'), - () => _runWebDebugTest('lib/stack_trace.dart'), - () => _runWebDebugTest('lib/framework_stack_trace.dart'), - () => _runWebDebugTest('lib/web_directory_loading.dart'), - () => _runWebDebugTest('lib/web_resources_cdn_test.dart', - additionalArguments: [ - '--dart-define=TEST_FLUTTER_ENGINE_VERSION=$engineVersion', - ]), - () => _runWebDebugTest('test/test.dart'), - () => _runWebDebugTest('lib/null_safe_main.dart'), - () => _runWebDebugTest('lib/web_define_loading.dart', - additionalArguments: [ - '--dart-define=test.valueA=Example,A', - '--dart-define=test.valueB=Value', - ] - ), - () => _runWebReleaseTest('lib/web_define_loading.dart', - additionalArguments: [ - '--dart-define=test.valueA=Example,A', - '--dart-define=test.valueB=Value', - ] - ), - () => _runWebDebugTest('lib/sound_mode.dart'), - () => _runWebReleaseTest('lib/sound_mode.dart'), - () => runFlutterWebTest( - 'html', - path.join(flutterRoot, 'packages', 'integration_test'), - ['test/web_extension_test.dart'], - false, - ), - () => runFlutterWebTest( - 'canvaskit', - path.join(flutterRoot, 'packages', 'integration_test'), - ['test/web_extension_test.dart'], - false, - ), - () => runFlutterWebTest( - 'skwasm', - path.join(flutterRoot, 'packages', 'integration_test'), - ['test/web_extension_test.dart'], - true, - ), - ]; - - // Shuffling mixes fast tests with slow tests so shards take roughly the same - // amount of time to run. - tests.shuffle(math.Random(0)); - - await _ensureChromeDriverIsRunning(); - await runShardRunnerIndexOfTotalSubshard(tests); - await _stopChromeDriver(); -} - -/// Runs one of the `dev/integration_tests/web_e2e_tests` tests. -Future _runWebE2eTest( - String name, { - required String buildMode, - required String renderer, - bool wasm = false, -}) async { - await _runFlutterDriverWebTest( - target: path.join('test_driver', '$name.dart'), - buildMode: buildMode, - renderer: renderer, - testAppDirectory: path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests'), - wasm: wasm, - ); -} - -Future _runFlutterDriverWebTest({ - required String target, - required String buildMode, - required String renderer, - required String testAppDirectory, - required bool wasm, - String? driver, - bool expectFailure = false, - bool silenceBrowserOutput = false, - bool expectWriteResponseFile = false, - String expectResponseFileContent = '', -}) async { - printProgress('${green}Running integration tests $target in $buildMode mode.$reset'); - await runCommand( - flutter, - [ 'clean' ], - workingDirectory: testAppDirectory, - ); - final String responseFile = - path.join(testAppDirectory, 'build', 'integration_response_data.json'); - if (File(responseFile).existsSync()) { - File(responseFile).deleteSync(); - } - await runCommand( - flutter, - [ - ...flutterTestArgs, - 'drive', - if (driver != null) '--driver=$driver', - '--target=$target', - '--browser-name=chrome', - '-d', - 'web-server', - '--$buildMode', - '--web-renderer=$renderer', - if (wasm) '--wasm', - ], - expectNonZeroExit: expectFailure, - workingDirectory: testAppDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - removeLine: (String line) { - if (!silenceBrowserOutput) { - return false; - } - if (line.trim().startsWith('[INFO]')) { - return true; - } - return false; - }, - ); - if (expectWriteResponseFile) { - if (!File(responseFile).existsSync()) { - foundError([ - '$bold${red}Command did not write the response file but expected response file written.$reset', - ]); - } else { - final String response = File(responseFile).readAsStringSync(); - if (response != expectResponseFileContent) { - foundError([ - '$bold${red}Command write the response file with $response but expected response file with $expectResponseFileContent.$reset', - ]); - } - } - } -} - -// Compiles a sample web app and checks that its JS doesn't contain certain -// debug code that we expect to be tree shaken out. -// -// The app is compiled in `--profile` mode to prevent the compiler from -// minifying the symbols. -Future _runWebTreeshakeTest() async { - final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests'); - final String target = path.join('lib', 'treeshaking_main.dart'); - await runCommand( - flutter, - [ 'clean' ], - workingDirectory: testAppDirectory, - ); - await runCommand( - flutter, - [ - 'build', - 'web', - '--target=$target', - '--profile', - ], - workingDirectory: testAppDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - ); - - final File mainDartJs = File(path.join(testAppDirectory, 'build', 'web', 'main.dart.js')); - final String javaScript = mainDartJs.readAsStringSync(); - - // Check that we're not looking at minified JS. Otherwise this test would result in false positive. - expect(javaScript.contains('RootElement'), true); - - const String word = 'debugFillProperties'; - int count = 0; - int pos = javaScript.indexOf(word); - final int contentLength = javaScript.length; - while (pos != -1) { - count += 1; - pos += word.length; - if (pos >= contentLength || count > 100) { - break; - } - pos = javaScript.indexOf(word, pos); - } - - // The following are classes from `timeline.dart` that should be treeshaken - // off unless the app (typically a benchmark) uses methods that need them. - expect(javaScript.contains('AggregatedTimedBlock'), false); - expect(javaScript.contains('AggregatedTimings'), false); - expect(javaScript.contains('_BlockBuffer'), false); - expect(javaScript.contains('_StringListChain'), false); - expect(javaScript.contains('_Float64ListChain'), false); - - const int kMaxExpectedDebugFillProperties = 11; - if (count > kMaxExpectedDebugFillProperties) { - throw Exception( - 'Too many occurrences of "$word" in compiled JavaScript.\n' - 'Expected no more than $kMaxExpectedDebugFillProperties, but found $count.' - ); - } -} - -/// Exercises the old gallery in a browser for a long period of time, looking -/// for memory leaks and dangling pointers. -/// -/// This is not a performance test. -/// -/// If [canvasKit] is set to true, runs the test in CanvasKit mode. -/// -/// The test is written using `package:integration_test` (despite the "e2e" in -/// the name, which is there for historic reasons). -Future _runGalleryE2eWebTest(String buildMode, { bool canvasKit = false }) async { - printProgress('${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset'); - final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery'); - await runCommand( - flutter, - [ 'clean' ], - workingDirectory: testAppDirectory, - ); - await runCommand( - flutter, - [ - ...flutterTestArgs, - 'drive', - if (canvasKit) - '--dart-define=FLUTTER_WEB_USE_SKIA=true', - if (!canvasKit) - '--dart-define=FLUTTER_WEB_USE_SKIA=false', - if (!canvasKit) - '--dart-define=FLUTTER_WEB_AUTO_DETECT=false', - '--driver=test_driver/transitions_perf_e2e_test.dart', - '--target=test_driver/transitions_perf_e2e.dart', - '--browser-name=chrome', - '-d', - 'web-server', - '--$buildMode', - ], - workingDirectory: testAppDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - ); -} - -Future _runWebStackTraceTest(String buildMode, String entrypoint) async { - final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); - final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); - - // Build the app. - await runCommand( - flutter, - [ 'clean' ], - workingDirectory: testAppDirectory, - ); - await runCommand( - flutter, - [ - 'build', - 'web', - '--$buildMode', - '-t', - entrypoint, - ], - workingDirectory: testAppDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - ); - - // Run the app. - final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests(); - final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests(); - final String result = await evalTestAppInChrome( - appUrl: 'http://localhost:$serverPort/index.html', - appDirectory: appBuildDirectory, - serverPort: serverPort, - browserDebugPort: browserDebugPort, - ); - - if (!result.contains('--- TEST SUCCEEDED ---')) { - foundError([ - result, - '${red}Web stack trace integration test failed.$reset', - ]); - } -} - -/// Debug mode is special because `flutter build web` doesn't build in debug mode. -/// -/// Instead, we use `flutter run --debug` and sniff out the standard output. -Future _runWebDebugTest(String target, { - List additionalArguments = const[], -}) async { - final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); - bool success = false; - final Map environment = { - 'FLUTTER_WEB': 'true', - }; - adjustEnvironmentToEnableFlutterAsserts(environment); - final CommandResult result = await runCommand( - flutter, - [ - 'run', - '--debug', - '-d', - 'chrome', - '--web-run-headless', - '--dart-define=FLUTTER_WEB_USE_SKIA=false', - '--dart-define=FLUTTER_WEB_AUTO_DETECT=false', - ...additionalArguments, - '-t', - target, - ], - outputMode: OutputMode.capture, - outputListener: (String line, Process process) { - if (line.contains('--- TEST SUCCEEDED ---')) { - success = true; - } - if (success || line.contains('--- TEST FAILED ---')) { - process.stdin.add('q'.codeUnits); - } - }, - workingDirectory: testAppDirectory, - environment: environment, - ); - - if (!success) { - foundError([ - result.flattenedStdout!, - result.flattenedStderr!, - '${red}Web stack trace integration test failed.$reset', - ]); - } -} - -/// Run a web integration test in release mode. -Future _runWebReleaseTest(String target, { - List additionalArguments = const[], -}) async { - final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); - final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); - - // Build the app. - await runCommand( - flutter, - [ 'clean' ], - workingDirectory: testAppDirectory, - ); - await runCommand( - flutter, - [ - ...flutterTestArgs, - 'build', - 'web', - '--release', - ...additionalArguments, - '-t', - target, - ], - workingDirectory: testAppDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - ); - - // Run the app. - final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests(); - final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests(); - final String result = await evalTestAppInChrome( - appUrl: 'http://localhost:$serverPort/index.html', - appDirectory: appBuildDirectory, - serverPort: serverPort, - browserDebugPort: browserDebugPort, - ); - - if (!result.contains('--- TEST SUCCEEDED ---')) { - foundError([ - result, - '${red}Web release mode test failed.$reset', - ]); - } -} - -// The `chromedriver` process created by this test. -// -// If an existing chromedriver is already available on port 4444, the existing -// process is reused and this variable remains null. -Command? _chromeDriver; - -Future _isChromeDriverRunning() async { - try { - final RawSocket socket = await RawSocket.connect('localhost', 4444); - socket.shutdown(SocketDirection.both); - await socket.close(); - return true; - } on SocketException { - return false; - } -} - -Future _stopChromeDriver() async { - if (_chromeDriver == null) { - return; - } - print('Stopping chromedriver'); - _chromeDriver!.process.kill(); -} - -Future _ensureChromeDriverIsRunning() async { - // If we cannot connect to ChromeDriver, assume it is not running. Launch it. - if (!await _isChromeDriverRunning()) { - printProgress('Starting chromedriver'); - // Assume chromedriver is in the PATH. - _chromeDriver = await startCommand( - // TODO(ianh): this is the only remaining consumer of startCommand other than runCommand - // and it doesn't use most of startCommand's features; we could simplify this a lot by - // inlining the relevant parts of startCommand here. - 'chromedriver', - ['--port=4444'], - ); - while (!await _isChromeDriverRunning()) { - await Future.delayed(const Duration(milliseconds: 100)); - print('Waiting for chromedriver to start up.'); - } - } - - final HttpClient client = HttpClient(); - final Uri chromeDriverUrl = Uri.parse('http://localhost:4444/status'); - final HttpClientRequest request = await client.getUrl(chromeDriverUrl); - final HttpClientResponse response = await request.close(); - final Map webDriverStatus = json.decode(await response.transform(utf8.decoder).join()) as Map; - client.close(); - final bool webDriverReady = (webDriverStatus['value'] as Map)['ready'] as bool; - if (!webDriverReady) { - throw Exception('WebDriver not available.'); - } -} diff --git a/dev/bots/suite_runners/run_web_tests.dart b/dev/bots/suite_runners/run_web_tests.dart new file mode 100644 index 000000000000..d1ce7001d3d3 --- /dev/null +++ b/dev/bots/suite_runners/run_web_tests.dart @@ -0,0 +1,759 @@ +// 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 Directory, File, FileSystemEntity, HttpClient, HttpClientRequest, HttpClientResponse, Platform, Process, RawSocket, SocketDirection, SocketException; +import 'dart:math' as math; +import 'package:file/local.dart'; +import 'package:path/path.dart' as path; + +import '../browser.dart'; +import '../run_command.dart'; +import '../service_worker_test.dart'; +import '../utils.dart'; + +typedef ShardRunner = Future Function(); + +class WebTestsSuite { + + WebTestsSuite(this.flutterTestArgs); + + /// Tests that we don't run on Web. + /// + /// In general avoid adding new tests here. If a test cannot run on the web + /// because it fails at runtime, such as when a piece of functionality is not + /// implemented or not implementable on the web, prefer using `skip` in the + /// test code. Only add tests here that cannot be skipped using `skip`. For + /// example: + /// + /// * Test code cannot be compiled because it uses Dart VM-specific + /// functionality. In this case `skip` doesn't help because the code cannot + /// reach the point where it can even run the skipping logic. + /// * Migrations. It is OK to put tests here that need to be temporarily + /// disabled in certain modes because of some migration or initial bringup. + /// + /// The key in the map is the renderer type that the list applies to. The value + /// is the list of tests known to fail for that renderer. + // + // TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60 + static const Map> kWebTestFileKnownFailures = >{ + 'html': [ + // These tests are not compilable on the web due to dependencies on + // VM-specific functionality. + 'test/services/message_codecs_vm_test.dart', + 'test/examples/sector_layout_test.dart', + ], + 'canvaskit': [ + // These tests are not compilable on the web due to dependencies on + // VM-specific functionality. + 'test/services/message_codecs_vm_test.dart', + 'test/examples/sector_layout_test.dart', + + // These tests are broken and need to be fixed. + // TODO(yjbanov): https://github.com/flutter/flutter/issues/71604 + 'test/material/text_field_test.dart', + 'test/widgets/performance_overlay_test.dart', + 'test/widgets/html_element_view_test.dart', + 'test/cupertino/scaffold_test.dart', + 'test/rendering/platform_view_test.dart', + ], + 'skwasm': [ + // These tests are not compilable on the web due to dependencies on + // VM-specific functionality. + 'test/services/message_codecs_vm_test.dart', + 'test/examples/sector_layout_test.dart', + + // These tests are broken and need to be fixed. + // TODO(jacksongardner): https://github.com/flutter/flutter/issues/71604 + 'test/material/text_field_test.dart', + 'test/widgets/performance_overlay_test.dart', + ], + }; + + /// The number of Cirrus jobs that run Web tests in parallel. + /// + /// The default is 8 shards. Typically .cirrus.yml would define the + /// WEB_SHARD_COUNT environment variable rather than relying on the default. + /// + /// WARNING: if you change this number, also change .cirrus.yml + /// and make sure it runs _all_ shards. + /// + /// The last shard also runs the Web plugin tests. + int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT') + ? int.parse(Platform.environment['WEB_SHARD_COUNT']!) + : 8; + + + static const List _kAllBuildModes = ['debug', 'profile', 'release']; + + final List flutterTestArgs; + + /// Coarse-grained integration tests running on the Web. + Future webLongRunningTestsRunner() async { + + final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engine.version'); + final String engineRealmFile = path.join(flutterRoot, 'bin', 'internal', 'engine.realm'); + final String engineVersion = File(engineVersionFile).readAsStringSync().trim(); + final String engineRealm = File(engineRealmFile).readAsStringSync().trim(); + if (engineRealm.isNotEmpty) { + return; + } + final List tests = [ + for (final String buildMode in _kAllBuildModes) ...[ + () => _runFlutterDriverWebTest( + testAppDirectory: path.join('packages', 'integration_test', 'example'), + target: path.join('test_driver', 'failure.dart'), + buildMode: buildMode, + renderer: 'canvaskit', + // This test intentionally fails and prints stack traces in the browser + // logs. To avoid confusion, silence browser output. + silenceBrowserOutput: true, + ), + () => _runFlutterDriverWebTest( + testAppDirectory: path.join('packages', 'integration_test', 'example'), + target: path.join('integration_test', 'example_test.dart'), + driver: path.join('test_driver', 'integration_test.dart'), + buildMode: buildMode, + renderer: 'canvaskit', + expectWriteResponseFile: true, + expectResponseFileContent: 'null', + ), + () => _runFlutterDriverWebTest( + testAppDirectory: path.join('packages', 'integration_test', 'example'), + target: path.join('integration_test', 'extended_test.dart'), + driver: path.join('test_driver', 'extended_integration_test.dart'), + buildMode: buildMode, + renderer: 'canvaskit', + expectWriteResponseFile: true, + expectResponseFileContent: ''' +{ + "screenshots": [ + { + "screenshotName": "platform_name", + "bytes": [] + }, + { + "screenshotName": "platform_name_2", + "bytes": [] + } + ] +}''', + ), + ], + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + () => _runWebE2eTest('platform_messages_integration', buildMode: 'debug', renderer: 'canvaskit'), + () => _runWebE2eTest('platform_messages_integration', buildMode: 'profile', renderer: 'html'), + () => _runWebE2eTest('platform_messages_integration', buildMode: 'release', renderer: 'html'), + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'debug', renderer: 'html'), + () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'profile', renderer: 'canvaskit'), + () => _runWebE2eTest('profile_diagnostics_integration', buildMode: 'release', renderer: 'html'), + + // This test is only known to work in debug mode. + () => _runWebE2eTest('scroll_wheel_integration', buildMode: 'debug', renderer: 'html'), + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + // These tests have been extremely flaky, so we are temporarily disabling them until we figure out how to make them more robust. + // See https://github.com/flutter/flutter/issues/143834 + // () => _runWebE2eTest('text_editing_integration', buildMode: 'debug', renderer: 'canvaskit'), + // () => _runWebE2eTest('text_editing_integration', buildMode: 'profile', renderer: 'html'), + // () => _runWebE2eTest('text_editing_integration', buildMode: 'release', renderer: 'html'), + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + () => _runWebE2eTest('url_strategy_integration', buildMode: 'debug', renderer: 'html'), + () => _runWebE2eTest('url_strategy_integration', buildMode: 'profile', renderer: 'canvaskit'), + () => _runWebE2eTest('url_strategy_integration', buildMode: 'release', renderer: 'html'), + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + () => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'debug', renderer: 'auto'), + () => _runWebE2eTest('capabilities_integration_canvaskit', buildMode: 'profile', renderer: 'canvaskit'), + () => _runWebE2eTest('capabilities_integration_html', buildMode: 'release', renderer: 'html'), + + // This test doesn't do anything interesting w.r.t. rendering, so we don't run the full build mode x renderer matrix. + // CacheWidth and CacheHeight are only currently supported in CanvasKit mode, so we don't run the test in HTML mode. + () => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'debug', renderer: 'auto'), + () => _runWebE2eTest('cache_width_cache_height_integration', buildMode: 'profile', renderer: 'canvaskit'), + + () => _runWebTreeshakeTest(), + + () => _runFlutterDriverWebTest( + testAppDirectory: path.join(flutterRoot, 'examples', 'hello_world'), + target: 'test_driver/smoke_web_engine.dart', + buildMode: 'profile', + renderer: 'auto', + ), + () => _runGalleryE2eWebTest('debug'), + () => _runGalleryE2eWebTest('debug', canvasKit: true), + () => _runGalleryE2eWebTest('profile'), + () => _runGalleryE2eWebTest('profile', canvasKit: true), + () => _runGalleryE2eWebTest('release'), + () => _runGalleryE2eWebTest('release', canvasKit: true), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJs), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn), + () => runWebServiceWorkerTest(headless: true, testType: ServiceWorkerTestType.withFlutterJsNonceOn), + () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withoutFlutterJs), + () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJs), + () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsShort), + () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsEntrypointLoadedEvent), + () => runWebServiceWorkerTestWithCachingResources(headless: true, testType: ServiceWorkerTestType.withFlutterJsTrustedTypesOn), + () => runWebServiceWorkerTestWithGeneratedEntrypoint(headless: true), + () => runWebServiceWorkerTestWithBlockedServiceWorkers(headless: true), + () => runWebServiceWorkerTestWithCustomServiceWorkerVersion(headless: true), + () => _runWebStackTraceTest('profile', 'lib/stack_trace.dart'), + () => _runWebStackTraceTest('release', 'lib/stack_trace.dart'), + () => _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'), + () => _runWebStackTraceTest('release', 'lib/framework_stack_trace.dart'), + () => _runWebDebugTest('lib/stack_trace.dart'), + () => _runWebDebugTest('lib/framework_stack_trace.dart'), + () => _runWebDebugTest('lib/web_directory_loading.dart'), + () => _runWebDebugTest('lib/web_resources_cdn_test.dart', + additionalArguments: [ + '--dart-define=TEST_FLUTTER_ENGINE_VERSION=$engineVersion', + ]), + () => _runWebDebugTest('test/test.dart'), + () => _runWebDebugTest('lib/null_safe_main.dart'), + () => _runWebDebugTest('lib/web_define_loading.dart', + additionalArguments: [ + '--dart-define=test.valueA=Example,A', + '--dart-define=test.valueB=Value', + ] + ), + () => _runWebReleaseTest('lib/web_define_loading.dart', + additionalArguments: [ + '--dart-define=test.valueA=Example,A', + '--dart-define=test.valueB=Value', + ] + ), + () => _runWebDebugTest('lib/sound_mode.dart'), + () => _runWebReleaseTest('lib/sound_mode.dart'), + () => _runFlutterWebTest( + 'html', + path.join(flutterRoot, 'packages', 'integration_test'), + ['test/web_extension_test.dart'], + false, + ), + () => _runFlutterWebTest( + 'canvaskit', + path.join(flutterRoot, 'packages', 'integration_test'), + ['test/web_extension_test.dart'], + false, + ), + () => _runFlutterWebTest( + 'skwasm', + path.join(flutterRoot, 'packages', 'integration_test'), + ['test/web_extension_test.dart'], + true, + ), + ]; + + // Shuffling mixes fast tests with slow tests so shards take roughly the same + // amount of time to run. + tests.shuffle(math.Random(0)); + + await _ensureChromeDriverIsRunning(); + await runShardRunnerIndexOfTotalSubshard(tests); + await _stopChromeDriver(); + } + + Future runWebHtmlUnitTests() { + return _runWebUnitTests('html', false); + } + + Future runWebCanvasKitUnitTests() { + return _runWebUnitTests('canvaskit', false); + } + + Future runWebSkwasmUnitTests() { + return _runWebUnitTests('skwasm', true); + } + + /// Runs one of the `dev/integration_tests/web_e2e_tests` tests. + Future _runWebE2eTest( + String name, { + required String buildMode, + required String renderer, + }) async { + await _runFlutterDriverWebTest( + target: path.join('test_driver', '$name.dart'), + buildMode: buildMode, + renderer: renderer, + testAppDirectory: path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests'), + ); + } + + Future _runFlutterDriverWebTest({ + required String target, + required String buildMode, + required String renderer, + required String testAppDirectory, + String? driver, + bool expectFailure = false, + bool silenceBrowserOutput = false, + bool expectWriteResponseFile = false, + String expectResponseFileContent = '', + }) async { + printProgress('${green}Running integration tests $target in $buildMode mode.$reset'); + await runCommand( + flutter, + [ 'clean' ], + workingDirectory: testAppDirectory, + ); + final String responseFile = + path.join(testAppDirectory, 'build', 'integration_response_data.json'); + if (File(responseFile).existsSync()) { + File(responseFile).deleteSync(); + } + await runCommand( + flutter, + [ + ...flutterTestArgs, + 'drive', + if (driver != null) '--driver=$driver', + '--target=$target', + '--browser-name=chrome', + '-d', + 'web-server', + '--$buildMode', + '--web-renderer=$renderer', + ], + expectNonZeroExit: expectFailure, + workingDirectory: testAppDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + removeLine: (String line) { + if (!silenceBrowserOutput) { + return false; + } + if (line.trim().startsWith('[INFO]')) { + return true; + } + return false; + }, + ); + if (expectWriteResponseFile) { + if (!File(responseFile).existsSync()) { + foundError([ + '$bold${red}Command did not write the response file but expected response file written.$reset', + ]); + } else { + final String response = File(responseFile).readAsStringSync(); + if (response != expectResponseFileContent) { + foundError([ + '$bold${red}Command write the response file with $response but expected response file with $expectResponseFileContent.$reset', + ]); + } + } + } + } + + // Compiles a sample web app and checks that its JS doesn't contain certain + // debug code that we expect to be tree shaken out. + // + // The app is compiled in `--profile` mode to prevent the compiler from + // minifying the symbols. + Future _runWebTreeshakeTest() async { + final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web_e2e_tests'); + final String target = path.join('lib', 'treeshaking_main.dart'); + await runCommand( + flutter, + [ 'clean' ], + workingDirectory: testAppDirectory, + ); + await runCommand( + flutter, + [ + 'build', + 'web', + '--target=$target', + '--profile', + ], + workingDirectory: testAppDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + + final File mainDartJs = File(path.join(testAppDirectory, 'build', 'web', 'main.dart.js')); + final String javaScript = mainDartJs.readAsStringSync(); + + // Check that we're not looking at minified JS. Otherwise this test would result in false positive. + expect(javaScript.contains('RootElement'), true); + + const String word = 'debugFillProperties'; + int count = 0; + int pos = javaScript.indexOf(word); + final int contentLength = javaScript.length; + while (pos != -1) { + count += 1; + pos += word.length; + if (pos >= contentLength || count > 100) { + break; + } + pos = javaScript.indexOf(word, pos); + } + + // The following are classes from `timeline.dart` that should be treeshaken + // off unless the app (typically a benchmark) uses methods that need them. + expect(javaScript.contains('AggregatedTimedBlock'), false); + expect(javaScript.contains('AggregatedTimings'), false); + expect(javaScript.contains('_BlockBuffer'), false); + expect(javaScript.contains('_StringListChain'), false); + expect(javaScript.contains('_Float64ListChain'), false); + + const int kMaxExpectedDebugFillProperties = 11; + if (count > kMaxExpectedDebugFillProperties) { + throw Exception( + 'Too many occurrences of "$word" in compiled JavaScript.\n' + 'Expected no more than $kMaxExpectedDebugFillProperties, but found $count.' + ); + } + } + + /// Exercises the old gallery in a browser for a long period of time, looking + /// for memory leaks and dangling pointers. + /// + /// This is not a performance test. + /// + /// If [canvasKit] is set to true, runs the test in CanvasKit mode. + /// + /// The test is written using `package:integration_test` (despite the "e2e" in + /// the name, which is there for historic reasons). + Future _runGalleryE2eWebTest(String buildMode, { bool canvasKit = false }) async { + printProgress('${green}Running flutter_gallery integration test in --$buildMode using ${canvasKit ? 'CanvasKit' : 'HTML'} renderer.$reset'); + final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery'); + await runCommand( + flutter, + [ 'clean' ], + workingDirectory: testAppDirectory, + ); + await runCommand( + flutter, + [ + ...flutterTestArgs, + 'drive', + if (canvasKit) + '--dart-define=FLUTTER_WEB_USE_SKIA=true', + if (!canvasKit) + '--dart-define=FLUTTER_WEB_USE_SKIA=false', + if (!canvasKit) + '--dart-define=FLUTTER_WEB_AUTO_DETECT=false', + '--driver=test_driver/transitions_perf_e2e_test.dart', + '--target=test_driver/transitions_perf_e2e.dart', + '--browser-name=chrome', + '-d', + 'web-server', + '--$buildMode', + ], + workingDirectory: testAppDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + } + + Future _runWebStackTraceTest(String buildMode, String entrypoint) async { + final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); + final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); + + // Build the app. + await runCommand( + flutter, + [ 'clean' ], + workingDirectory: testAppDirectory, + ); + await runCommand( + flutter, + [ + 'build', + 'web', + '--$buildMode', + '-t', + entrypoint, + ], + workingDirectory: testAppDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + + // Run the app. + final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + final String result = await evalTestAppInChrome( + appUrl: 'http://localhost:$serverPort/index.html', + appDirectory: appBuildDirectory, + serverPort: serverPort, + browserDebugPort: browserDebugPort, + ); + + if (!result.contains('--- TEST SUCCEEDED ---')) { + foundError([ + result, + '${red}Web stack trace integration test failed.$reset', + ]); + } + } + + /// Debug mode is special because `flutter build web` doesn't build in debug mode. + /// + /// Instead, we use `flutter run --debug` and sniff out the standard output. + Future _runWebDebugTest(String target, { + List additionalArguments = const[], + }) async { + final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); + bool success = false; + final Map environment = { + 'FLUTTER_WEB': 'true', + }; + adjustEnvironmentToEnableFlutterAsserts(environment); + final CommandResult result = await runCommand( + flutter, + [ + 'run', + '--debug', + '-d', + 'chrome', + '--web-run-headless', + '--dart-define=FLUTTER_WEB_USE_SKIA=false', + '--dart-define=FLUTTER_WEB_AUTO_DETECT=false', + ...additionalArguments, + '-t', + target, + ], + outputMode: OutputMode.capture, + outputListener: (String line, Process process) { + if (line.contains('--- TEST SUCCEEDED ---')) { + success = true; + } + if (success || line.contains('--- TEST FAILED ---')) { + process.stdin.add('q'.codeUnits); + } + }, + workingDirectory: testAppDirectory, + environment: environment, + ); + + if (!success) { + foundError([ + result.flattenedStdout!, + result.flattenedStderr!, + '${red}Web stack trace integration test failed.$reset', + ]); + } + } + + /// Run a web integration test in release mode. + Future _runWebReleaseTest(String target, { + List additionalArguments = const[], + }) async { + final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); + final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); + + // Build the app. + await runCommand( + flutter, + [ 'clean' ], + workingDirectory: testAppDirectory, + ); + await runCommand( + flutter, + [ + ...flutterTestArgs, + 'build', + 'web', + '--release', + ...additionalArguments, + '-t', + target, + ], + workingDirectory: testAppDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + + // Run the app. + final int serverPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + final int browserDebugPort = await findAvailablePortAndPossiblyCauseFlakyTests(); + final String result = await evalTestAppInChrome( + appUrl: 'http://localhost:$serverPort/index.html', + appDirectory: appBuildDirectory, + serverPort: serverPort, + browserDebugPort: browserDebugPort, + ); + + if (!result.contains('--- TEST SUCCEEDED ---')) { + foundError([ + result, + '${red}Web release mode test failed.$reset', + ]); + } + } + + Future _runWebUnitTests(String webRenderer, bool useWasm) async { + final Map subshards = {}; + + final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter')); + final Directory flutterPackageTestDirectory = Directory(path.join(flutterPackageDirectory.path, 'test')); + + final List allTests = flutterPackageTestDirectory + .listSync() + .whereType() + .expand((Directory directory) => directory + .listSync(recursive: true) + .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart')) + ) + .whereType() + .map((File file) => path.relative(file.path, from: flutterPackageDirectory.path)) + .where((String filePath) => !kWebTestFileKnownFailures[webRenderer]!.contains(path.split(filePath).join('/'))) + .toList() + // Finally we shuffle the list because we want the average cost per file to be uniformly + // distributed. If the list is not sorted then different shards and batches may have + // very different characteristics. + // We use a constant seed for repeatability. + ..shuffle(math.Random(0)); + + assert(webShardCount >= 1); + final int testsPerShard = (allTests.length / webShardCount).ceil(); + assert(testsPerShard * webShardCount >= allTests.length); + + // This for loop computes all but the last shard. + for (int index = 0; index < webShardCount - 1; index += 1) { + subshards['$index'] = () => _runFlutterWebTest( + webRenderer, + flutterPackageDirectory.path, + allTests.sublist( + index * testsPerShard, + (index + 1) * testsPerShard, + ), + useWasm, + ); + } + + // The last shard also runs the flutter_web_plugins tests. + // + // We make sure the last shard ends in _last so it's easier to catch mismatches + // between `.cirrus.yml` and `test.dart`. + subshards['${webShardCount - 1}_last'] = () async { + await _runFlutterWebTest( + webRenderer, + flutterPackageDirectory.path, + allTests.sublist( + (webShardCount - 1) * testsPerShard, + allTests.length, + ), + useWasm, + ); + await _runFlutterWebTest( + webRenderer, + path.join(flutterRoot, 'packages', 'flutter_web_plugins'), + ['test'], + useWasm, + ); + await _runFlutterWebTest( + webRenderer, + path.join(flutterRoot, 'packages', 'flutter_driver'), + [path.join('test', 'src', 'web_tests', 'web_extension_test.dart')], + useWasm, + ); + }; + + await selectSubshard(subshards); + } + + Future _runFlutterWebTest( + String webRenderer, + String workingDirectory, + List tests, + bool useWasm, + ) async { + const LocalFileSystem fileSystem = LocalFileSystem(); + final String suffix = DateTime.now().microsecondsSinceEpoch.toString(); + final File metricFile = fileSystem.systemTempDirectory.childFile('metrics_$suffix.json'); + await runCommand( + flutter, + [ + 'test', + '--reporter=expanded', + '--file-reporter=json:${metricFile.path}', + '-v', + '--platform=chrome', + if (useWasm) '--wasm', + '--web-renderer=$webRenderer', + '--dart-define=DART_HHH_BOT=$runningInDartHHHBot', + ...flutterTestArgs, + ...tests, + ], + workingDirectory: workingDirectory, + environment: { + 'FLUTTER_WEB': 'true', + }, + ); + // 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(); + } + + // The `chromedriver` process created by this test. + // + // If an existing chromedriver is already available on port 4444, the existing + // process is reused and this variable remains null. + Command? _chromeDriver; + + Future _isChromeDriverRunning() async { + try { + final RawSocket socket = await RawSocket.connect('localhost', 4444); + socket.shutdown(SocketDirection.both); + await socket.close(); + return true; + } on SocketException { + return false; + } + } + + Future _stopChromeDriver() async { + if (_chromeDriver == null) { + return; + } + print('Stopping chromedriver'); + _chromeDriver!.process.kill(); + } + + Future _ensureChromeDriverIsRunning() async { + // If we cannot connect to ChromeDriver, assume it is not running. Launch it. + if (!await _isChromeDriverRunning()) { + printProgress('Starting chromedriver'); + // Assume chromedriver is in the PATH. + _chromeDriver = await startCommand( + // TODO(ianh): this is the only remaining consumer of startCommand other than runCommand + // and it doesn't use most of startCommand's features; we could simplify this a lot by + // inlining the relevant parts of startCommand here. + 'chromedriver', + ['--port=4444'], + ); + while (!await _isChromeDriverRunning()) { + await Future.delayed(const Duration(milliseconds: 100)); + print('Waiting for chromedriver to start up.'); + } + } + + final HttpClient client = HttpClient(); + final Uri chromeDriverUrl = Uri.parse('http://localhost:4444/status'); + final HttpClientRequest request = await client.getUrl(chromeDriverUrl); + final HttpClientResponse response = await request.close(); + final Map webDriverStatus = json.decode(await response.transform(utf8.decoder).join()) as Map; + client.close(); + final bool webDriverReady = (webDriverStatus['value'] as Map)['ready'] as bool; + if (!webDriverReady) { + throw Exception('WebDriver not available.'); + } + } +} diff --git a/dev/bots/test.dart b/dev/bots/test.dart index cb1dab7cd9d4..86312350ea26 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -53,154 +53,33 @@ import 'dart:core' hide print; import 'dart:io' as system show exit; import 'dart:io' hide exit; import 'dart:math' as math; -import 'dart:typed_data'; -import 'package:archive/archive.dart'; -import 'package:file/file.dart' as fs; -import 'package:file/local.dart'; import 'package:path/path.dart' as path; import 'run_command.dart'; import 'suite_runners/run_add_to_app_life_cycle_tests.dart'; import 'suite_runners/run_analyze_tests.dart'; +import 'suite_runners/run_android_preview_integration_tool_tests.dart'; import 'suite_runners/run_customer_testing_tests.dart'; import 'suite_runners/run_docs_tests.dart'; import 'suite_runners/run_flutter_packages_tests.dart'; +import 'suite_runners/run_framework_coverage_tests.dart'; +import 'suite_runners/run_framework_tests.dart'; import 'suite_runners/run_fuchsia_precache.dart'; import 'suite_runners/run_realm_checker_tests.dart'; import 'suite_runners/run_skp_generator_tests.dart'; +import 'suite_runners/run_test_harness_tests.dart'; import 'suite_runners/run_verify_binaries_codesigned_tests.dart'; -import 'suite_runners/run_web_long_running_tests.dart'; -import 'tool_subsharding.dart'; +import 'suite_runners/run_web_tests.dart'; import 'utils.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); - -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'); - -String get platformFolderName { - if (Platform.isWindows) { - return 'windows-x64'; - } - if (Platform.isMacOS) { - return 'darwin-x64'; - } - if (Platform.isLinux) { - return 'linux-x64'; - } - throw UnsupportedError('The platform ${Platform.operatingSystem} is not supported by this script.'); -} -final String flutterTester = path.join(flutterRoot, 'bin', 'cache', 'artifacts', 'engine', platformFolderName, 'flutter_tester$exe'); - -/// The arguments to pass to `flutter test` (typically the local engine -/// configuration) -- prefilled with the arguments passed to test.dart. -final List flutterTestArgs = []; - /// Environment variables to override the local engine when running `pub test`, /// if such flags are provided to `test.dart`. final Map localEngineEnv = {}; -const String kShardKey = 'SHARD'; -const String kSubshardKey = 'SUBSHARD'; - -/// The number of Cirrus jobs that run Web tests in parallel. -/// -/// The default is 8 shards. Typically .cirrus.yml would define the -/// WEB_SHARD_COUNT environment variable rather than relying on the default. -/// -/// WARNING: if you change this number, also change .cirrus.yml -/// and make sure it runs _all_ shards. -/// -/// The last shard also runs the Web plugin tests. -int get webShardCount => Platform.environment.containsKey('WEB_SHARD_COUNT') - ? int.parse(Platform.environment['WEB_SHARD_COUNT']!) - : 8; - -/// Tests that we don't run on Web. -/// -/// In general avoid adding new tests here. If a test cannot run on the web -/// because it fails at runtime, such as when a piece of functionality is not -/// implemented or not implementable on the web, prefer using `skip` in the -/// test code. Only add tests here that cannot be skipped using `skip`. For -/// example: -/// -/// * Test code cannot be compiled because it uses Dart VM-specific -/// functionality. In this case `skip` doesn't help because the code cannot -/// reach the point where it can even run the skipping logic. -/// * Migrations. It is OK to put tests here that need to be temporarily -/// disabled in certain modes because of some migration or initial bringup. -/// -/// The key in the map is the renderer type that the list applies to. The value -/// is the list of tests known to fail for that renderer. -// -// TODO(yjbanov): we're getting rid of this as part of https://github.com/flutter/flutter/projects/60 -const Map> kWebTestFileKnownFailures = >{ - 'html': [ - // These tests are not compilable on the web due to dependencies on - // VM-specific functionality. - 'test/services/message_codecs_vm_test.dart', - 'test/examples/sector_layout_test.dart', - ], - 'canvaskit': [ - // These tests are not compilable on the web due to dependencies on - // VM-specific functionality. - 'test/services/message_codecs_vm_test.dart', - 'test/examples/sector_layout_test.dart', - - // These tests are broken and need to be fixed. - // TODO(yjbanov): https://github.com/flutter/flutter/issues/71604 - 'test/material/text_field_test.dart', - 'test/widgets/performance_overlay_test.dart', - 'test/widgets/html_element_view_test.dart', - 'test/cupertino/scaffold_test.dart', - 'test/rendering/platform_view_test.dart', - ], - 'skwasm': [ - // These tests are not compilable on the web due to dependencies on - // VM-specific functionality. - 'test/services/message_codecs_vm_test.dart', - 'test/examples/sector_layout_test.dart', - - // These tests are broken and need to be fixed. - // TODO(jacksongardner): https://github.com/flutter/flutter/issues/71604 - 'test/material/text_field_test.dart', - 'test/widgets/performance_overlay_test.dart', - ], -}; - -const String kTestHarnessShardName = 'test_harness_tests'; - -// 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; -String get shuffleSeed { - if (_shuffleSeed == null) { - // Change the seed at 7am, UTC. - final DateTime seedTime = DateTime.now().toUtc().subtract(const Duration(hours: 7)); - // Generates YYYYMMDD as the seed, so that testing continues to fail for a - // day after the seed changes, and on other days the seed can be used to - // replicate failures. - _shuffleSeed = '${seedTime.year * 10000 + seedTime.month * 100 + seedTime.day}'; - } - return _shuffleSeed!; -} - -final bool _isRandomizationOff = bool.tryParse(Platform.environment['TEST_RANDOMIZATION_OFF'] ?? '') ?? false; +const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME'; /// When you call this, you can pass additional arguments to pass custom /// arguments to flutter test. For example, you might want to call this @@ -226,7 +105,7 @@ Future main(List args) async { localEngineEnv['FLUTTER_LOCAL_ENGINE_SRC_PATH'] = arg.substring('--local-engine-src-path='.length); flutterTestArgs.add(arg); } else if (arg.startsWith('--test-randomize-ordering-seed=')) { - _shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length); + shuffleSeed = arg.substring('--test-randomize-ordering-seed='.length); } else if (arg.startsWith('--verbose')) { print = (Object? message) { system.print(message); @@ -242,34 +121,34 @@ Future main(List args) async { if (Platform.environment.containsKey(CIRRUS_TASK_NAME)) { printProgress('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}'); } + final WebTestsSuite webTestsSuite = WebTestsSuite(flutterTestArgs); await selectShard({ - 'add_to_app_life_cycle_tests': () => addToAppLifeCycleRunner(flutterRoot), + 'add_to_app_life_cycle_tests': addToAppLifeCycleRunner, 'build_tests': _runBuildTests, - 'framework_coverage': _runFrameworkCoverage, - 'framework_tests': _runFrameworkTests, + 'framework_coverage': frameworkCoverageRunner, + 'framework_tests': frameworkTestsRunner, 'tool_tests': _runToolTests, - // web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py 'web_tool_tests': _runWebToolTests, 'tool_integration_tests': _runIntegrationToolTests, - 'android_preview_tool_integration_tests': _runAndroidPreviewIntegrationToolTests, + 'android_preview_tool_integration_tests': androidPreviewIntegrationToolTestsRunner, 'tool_host_cross_arch_tests': _runToolHostCrossArchTests, // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=html` - 'web_tests': _runWebHtmlUnitTests, + 'web_tests': webTestsSuite.runWebHtmlUnitTests, // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit` - 'web_canvaskit_tests': _runWebCanvasKitUnitTests, + 'web_canvaskit_tests': webTestsSuite.runWebCanvasKitUnitTests, // All the unit/widget tests run using `flutter test --platform=chrome --wasm --web-renderer=skwasm` - 'web_skwasm_tests': _runWebSkwasmUnitTests, + 'web_skwasm_tests': webTestsSuite.runWebSkwasmUnitTests, // All web integration tests - 'web_long_running_tests': () => webLongRunningTestsRunner(flutterRoot), - 'flutter_plugins': () => flutterPackagesRunner(flutterRoot), + 'web_long_running_tests': webTestsSuite.webLongRunningTestsRunner, + 'flutter_plugins': flutterPackagesRunner, 'skp_generator': skpGeneratorTestsRunner, - 'realm_checker': () => realmCheckerTestRunner(flutterRoot), - 'customer_testing': () => customerTestingRunner(flutterRoot), - 'analyze': () => analyzeRunner(flutterRoot), - 'fuchsia_precache': () => fuchsiaPrecacheRunner(flutterRoot), - 'docs': () => docsRunner(flutterRoot), - 'verify_binaries_codesigned': () => verifyCodesignedTestRunner(flutterRoot), - kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc. + 'realm_checker': realmCheckerTestRunner, + 'customer_testing': customerTestingRunner, + 'analyze': analyzeRunner, + 'fuchsia_precache': fuchsiaPrecacheRunner, + 'docs': docsRunner, + 'verify_binaries_codesigned': verifyCodesignedTestRunner, + kTestHarnessShardName: testHarnessTestsRunner, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc. }); } catch (error, stackTrace) { foundError([ @@ -287,158 +166,10 @@ Future main(List args) async { reportSuccessAndExit('${bold}Test successful.$reset'); } -final String _luciBotId = Platform.environment['SWARMING_BOT_ID'] ?? ''; -final bool _runningInDartHHHBot = - _luciBotId.startsWith('luci-dart-') || _luciBotId.startsWith('dart-tests-'); - -/// Verify the Flutter Engine is the revision in -/// bin/cache/internal/engine.version. -Future _validateEngineHash() async { - if (_runningInDartHHHBot) { - // The Dart HHH bots intentionally modify the local artifact cache - // and then use this script to run Flutter's test suites. - // Because the artifacts have been changed, this particular test will return - // a false positive and should be skipped. - print('${yellow}Skipping Flutter Engine Version Validation for swarming bot $_luciBotId.'); - return; - } - final String expectedVersion = File(engineVersionFile).readAsStringSync().trim(); - final CommandResult result = await runCommand(flutterTester, ['--help'], outputMode: OutputMode.capture); - if (result.flattenedStdout!.isNotEmpty) { - foundError([ - '${red}The stdout of `$flutterTester --help` was not empty:$reset', - ...result.flattenedStdout!.split('\n').map((String line) => ' $gray┆$reset $line'), - ]); - } - final String actualVersion; - try { - actualVersion = result.flattenedStderr!.split('\n').firstWhere((final String line) { - return line.startsWith('Flutter Engine Version:'); - }); - } on StateError { - foundError([ - '${red}Could not find "Flutter Engine Version:" line in `${path.basename(flutterTester)} --help` stderr output:$reset', - ...result.flattenedStderr!.split('\n').map((String line) => ' $gray┆$reset $line'), - ]); - return; - } - if (!actualVersion.contains(expectedVersion)) { - foundError(['${red}Expected "Flutter Engine Version: $expectedVersion", but found "$actualVersion".$reset']); - } -} - -Future _runTestHarnessTests() async { - printProgress('${green}Running test harness tests...$reset'); - - await _validateEngineHash(); - - // Verify that the tests actually return failure on failure and success on - // success. - final String automatedTests = path.join(flutterRoot, 'dev', 'automated_tests'); - - // We want to run these tests in parallel, because they each take some time - // to run (e.g. compiling), so we don't want to run them in series, especially - // on 20-core machines. However, we have a race condition, so for now... - // Race condition issue: https://github.com/flutter/flutter/issues/90026 - final List tests = [ - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'pass_test.dart'), - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'fail_test.dart'), - expectFailure: true, - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'), - expectFailure: true, - printOutput: false, - outputChecker: (CommandResult result) { - return result.flattenedStdout!.contains('failingPendingTimerTest') - ? null - : 'Failed to find the stack trace for the pending Timer.\n\n' - 'stdout:\n${result.flattenedStdout}\n\n' - 'stderr:\n${result.flattenedStderr}'; - }, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'fail_test_on_exception_after_test.dart'), - expectFailure: true, - printOutput: false, - outputChecker: (CommandResult result) { - const String expectedError = '══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════\n' - 'The following StateError was thrown running a test (but after the test had completed):\n' - 'Bad state: Exception thrown after test completed.'; - if (result.flattenedStdout!.contains(expectedError)) { - return null; - } - return 'Failed to find expected output on stdout.\n\n' - 'Expected output:\n$expectedError\n\n' - 'Actual stdout:\n${result.flattenedStdout}\n\n' - 'Actual stderr:\n${result.flattenedStderr}'; - }, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'crash1_test.dart'), - expectFailure: true, - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'crash2_test.dart'), - expectFailure: true, - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'syntax_error_test.broken_dart'), - expectFailure: true, - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'missing_import_test.broken_dart'), - expectFailure: true, - printOutput: false, - ), - () => _runFlutterTest( - automatedTests, - script: path.join('test_smoke_test', 'disallow_error_reporter_modification_test.dart'), - expectFailure: true, - printOutput: false, - ), - ]; - - List testsToRun; - - // Run all tests unless sharding is explicitly specified. - final String? shardName = Platform.environment[kShardKey]; - if (shardName == kTestHarnessShardName) { - testsToRun = _selectIndexOfTotalSubshard(tests); - } else { - testsToRun = tests; - } - for (final ShardRunner test in testsToRun) { - await test(); - } - - // Verify that we correctly generated the version file. - final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); - if (versionError != null) { - foundError([versionError]); - } -} - final String _toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools'); Future _runGeneralToolTests() async { - await _runDartTest( + await runDartTest( _toolsPath, testPaths: [path.join('test', 'general.shard')], enableFlutterToolAsserts: false, @@ -451,7 +182,7 @@ Future _runGeneralToolTests() async { } Future _runCommandsToolTests() async { - await _runDartTest( + await runDartTest( _toolsPath, forceSingleCore: true, testPaths: [path.join('test', 'commands.shard')], @@ -467,16 +198,16 @@ Future _runWebToolTests() async { allTests.add(file.path); } } - await _runDartTest( + await runDartTest( _toolsPath, forceSingleCore: true, - testPaths: _selectIndexOfTotalSubshard(allTests), + testPaths: selectIndexOfTotalSubshard(allTests), includeLocalEngineEnv: true, ); } Future _runToolHostCrossArchTests() { - return _runDartTest( + return runDartTest( _toolsPath, // These are integration tests forceSingleCore: true, @@ -490,24 +221,10 @@ Future _runIntegrationToolTests() async { .map((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath)) .where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList(); - await _runDartTest( - _toolsPath, - forceSingleCore: true, - testPaths: _selectIndexOfTotalSubshard(allTests), - collectMetrics: true, - ); -} - -Future _runAndroidPreviewIntegrationToolTests() async { - final List allTests = Directory(path.join(_toolsPath, 'test', 'android_preview_integration.shard')) - .listSync(recursive: true).whereType() - .map((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath)) - .where((String testPath) => path.basename(testPath).endsWith('_test.dart')).toList(); - - await _runDartTest( + await runDartTest( _toolsPath, forceSingleCore: true, - testPaths: _selectIndexOfTotalSubshard(allTests), + testPaths: selectIndexOfTotalSubshard(allTests), collectMetrics: true, ); } @@ -808,740 +525,3 @@ Future _flutterBuildDart2js(String relativePathToApplication, String targe }, ); } - -Future _runFrameworkTests() async { - final List trackWidgetCreationAlternatives = ['--track-widget-creation', '--no-track-widget-creation']; - - Future runWidgets() async { - printProgress('${green}Running packages/flutter tests $reset for ${cyan}test/widgets/$reset'); - for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter'), - options: [trackWidgetCreationOption], - tests: [ path.join('test', 'widgets') + path.separator ], - ); - } - // Try compiling code outside of the packages/flutter directory with and without --track-widget-creation - for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { - await _runFlutterTest( - path.join(flutterRoot, 'dev', 'integration_tests', 'flutter_gallery'), - options: [trackWidgetCreationOption], - fatalWarnings: false, // until we've migrated video_player - ); - } - // Run release mode tests (see packages/flutter/test_release/README.md) - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter'), - options: ['--dart-define=dart.vm.product=true'], - tests: ['test_release${path.separator}'], - ); - // Run profile mode tests (see packages/flutter/test_profile/README.md) - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter'), - options: ['--dart-define=dart.vm.product=false', '--dart-define=dart.vm.profile=true'], - tests: ['test_profile${path.separator}'], - ); - } - - Future runImpeller() async { - printProgress('${green}Running packages/flutter tests $reset in Impeller$reset'); - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter'), - options: ['--enable-impeller'], - ); - } - - - Future runLibraries() async { - final List tests = Directory(path.join(flutterRoot, 'packages', 'flutter', 'test')) - .listSync(followLinks: false) - .whereType() - .where((Directory dir) => !dir.path.endsWith('widgets')) - .map((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator) - .toList(); - printProgress('${green}Running packages/flutter tests$reset for $cyan${tests.join(", ")}$reset'); - for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) { - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter'), - options: [trackWidgetCreationOption], - tests: tests, - ); - } - } - - Future runExampleTests() async { - await runCommand( - flutter, - ['config', '--enable-${Platform.operatingSystem}-desktop'], - workingDirectory: flutterRoot, - ); - await runCommand( - dart, - [path.join(flutterRoot, 'dev', 'tools', 'examples_smoke_test.dart')], - workingDirectory: path.join(flutterRoot, 'examples', 'api'), - ); - for (final FileSystemEntity entity in Directory(path.join(flutterRoot, 'examples')).listSync()) { - if (entity is! Directory || !Directory(path.join(entity.path, 'test')).existsSync()) { - continue; - } - await _runFlutterTest(entity.path); - } - } - - Future runTracingTests() async { - final String tracingDirectory = path.join(flutterRoot, 'dev', 'tracing_tests'); - - // run the tests for debug mode - await _runFlutterTest(tracingDirectory, options: ['--enable-vmservice']); - - Future> verifyTracingAppBuild({ - required String modeArgument, - required String sourceFile, - required Set allowed, - required Set disallowed, - }) async { - try { - await runCommand( - flutter, - [ - 'build', 'appbundle', '--$modeArgument', path.join('lib', sourceFile), - ], - workingDirectory: tracingDirectory, - ); - final Archive archive = ZipDecoder().decodeBytes(File(path.join(tracingDirectory, 'build', 'app', 'outputs', 'bundle', modeArgument, 'app-$modeArgument.aab')).readAsBytesSync()); - final ArchiveFile libapp = archive.findFile('base/lib/arm64-v8a/libapp.so')!; - final Uint8List libappBytes = libapp.content as Uint8List; // bytes decompressed here - final String libappStrings = utf8.decode(libappBytes, allowMalformed: true); - await runCommand(flutter, ['clean'], workingDirectory: tracingDirectory); - final List results = []; - for (final String pattern in allowed) { - if (!libappStrings.contains(pattern)) { - results.add('When building with --$modeArgument, expected to find "$pattern" in libapp.so but could not find it.'); - } - } - for (final String pattern in disallowed) { - if (libappStrings.contains(pattern)) { - results.add('When building with --$modeArgument, expected to not find "$pattern" in libapp.so but did find it.'); - } - } - return results; - } catch (error, stackTrace) { - return [ - error.toString(), - ...stackTrace.toString().trimRight().split('\n'), - ]; - } - } - - final List results = []; - results.addAll(await verifyTracingAppBuild( - modeArgument: 'profile', - sourceFile: 'control.dart', // this is the control, the other two below are the actual test - allowed: { - 'TIMELINE ARGUMENTS TEST CONTROL FILE', - 'toTimelineArguments used in non-debug build', // we call toTimelineArguments directly to check the message does exist - }, - disallowed: { - 'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE', - }, - )); - results.addAll(await verifyTracingAppBuild( - modeArgument: 'profile', - sourceFile: 'test.dart', - allowed: { - 'BUILT IN PROFILE MODE', 'RenderTest.performResize called', // controls - 'BUILD', 'LAYOUT', 'PAINT', // we output these to the timeline in profile builds - // (LAYOUT and PAINT also exist because of NEEDS-LAYOUT and NEEDS-PAINT in RenderObject.toStringShort) - }, - disallowed: { - 'BUILT IN DEBUG MODE', 'BUILT IN RELEASE MODE', - 'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only - 'toTimelineArguments used in non-debug build', // entire function should get dropped by tree shaker - }, - )); - results.addAll(await verifyTracingAppBuild( - modeArgument: 'release', - sourceFile: 'test.dart', - allowed: { - 'BUILT IN RELEASE MODE', 'RenderTest.performResize called', // controls - }, - disallowed: { - 'BUILT IN DEBUG MODE', 'BUILT IN PROFILE MODE', - 'BUILD', 'LAYOUT', 'PAINT', // these are only used in Timeline.startSync calls that should not appear in release builds - 'TestWidget.debugFillProperties called', 'RenderTest.debugFillProperties called', // debug only - 'toTimelineArguments used in non-debug build', // not included in release builds - }, - )); - if (results.isNotEmpty) { - foundError(results); - } - } - - Future runFixTests(String package) async { - final List args = [ - 'fix', - '--compare-to-golden', - ]; - await runCommand( - dart, - args, - workingDirectory: path.join(flutterRoot, 'packages', package, 'test_fixes'), - ); - } - - Future runPrivateTests() async { - final List args = [ - 'run', - 'bin/test_private.dart', - ]; - final Map environment = { - 'FLUTTER_ROOT': flutterRoot, - if (Directory(pubCache).existsSync()) - 'PUB_CACHE': pubCache, - }; - adjustEnvironmentToEnableFlutterAsserts(environment); - await runCommand( - dart, - args, - workingDirectory: path.join(flutterRoot, 'packages', 'flutter', 'test_private'), - environment: environment, - ); - } - - // Tests that take longer than average to run. This is usually because they - // need to compile something large or make use of the analyzer for the test. - // These tests need to be platform agnostic as they are only run on a linux - // machine to save on execution time and cost. - Future runSlow() async { - printProgress('${green}Running slow package tests$reset for directories other than packages/flutter'); - await runTracingTests(); - await runFixTests('flutter'); - await runFixTests('flutter_test'); - await runFixTests('integration_test'); - await runFixTests('flutter_driver'); - await runPrivateTests(); - } - - Future runMisc() async { - printProgress('${green}Running package tests$reset for directories other than packages/flutter'); - await _runTestHarnessTests(); - await runExampleTests(); - await _runFlutterTest( - path.join(flutterRoot, 'dev', 'a11y_assessments'), - tests: [ 'test' ], - ); - await _runDartTest(path.join(flutterRoot, 'dev', 'bots')); - await _runDartTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 - await _runDartTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); - // TODO(gspencergoog): Remove the exception for fatalWarnings once https://github.com/flutter/flutter/issues/113782 has landed. - await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'ui')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_defaults')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks')); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: [path.join('test', 'src', 'real_tests')]); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: [ - '--enable-vmservice', - // Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard. - '--exclude-tags=web', - ]); - // Run java unit tests for integration_test - // - // Generate Gradle wrapper if it doesn't exist. - Process.runSync( - flutter, - ['build', 'apk', '--config-only'], - workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'), - ); - await runCommand( - path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android', 'gradlew$bat'), - [ - ':integration_test:testDebugUnitTest', - '--tests', - 'dev.flutter.plugins.integration_test.FlutterDeviceScreenshotTest', - ], - workingDirectory: path.join(flutterRoot, 'packages', 'integration_test', 'example', 'android'), - ); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens')); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations')); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test')); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol')); - await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable')); - const String httpClientWarning = - 'Warning: At least one test in this suite creates an HttpClient. When running a test suite that uses\n' - 'TestWidgetsFlutterBinding, all HTTP requests will return status code 400, and no network request\n' - 'will actually be made. Any test expecting a real network connection and status code will fail.\n' - 'To test code that needs an HttpClient, provide your own HttpClient implementation to the code under\n' - 'test, so that your test can consistently provide a testable response to the code under test.'; - await _runFlutterTest( - path.join(flutterRoot, 'packages', 'flutter_test'), - script: path.join('test', 'bindings_test_failure.dart'), - expectFailure: true, - printOutput: false, - outputChecker: (CommandResult result) { - final Iterable matches = httpClientWarning.allMatches(result.flattenedStdout!); - if (matches.isEmpty || matches.length > 1) { - return 'Failed to print warning about HttpClientUsage, or printed it too many times.\n\n' - 'stdout:\n${result.flattenedStdout}\n\n' - 'stderr:\n${result.flattenedStderr}'; - } - return null; - }, - ); - } - - await selectSubshard({ - 'widgets': runWidgets, - 'libraries': runLibraries, - 'slow': runSlow, - 'misc': runMisc, - 'impeller': runImpeller, - }); -} - -Future _runFrameworkCoverage() async { - final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info')); - if (!coverageFile.existsSync()) { - foundError([ - '${red}Coverage file not found.$reset', - 'Expected to find: $cyan${coverageFile.absolute.path}$reset', - 'This file is normally obtained by running `${green}flutter update-packages$reset`.', - ]); - return; - } - coverageFile.deleteSync(); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter'), - options: const ['--coverage'], - ); - if (!coverageFile.existsSync()) { - foundError([ - '${red}Coverage file not found.$reset', - 'Expected to find: $cyan${coverageFile.absolute.path}$reset', - 'This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.', - ]); - return; - } -} - -Future _runWebHtmlUnitTests() { - return _runWebUnitTests('html', false); -} - -Future _runWebCanvasKitUnitTests() { - return _runWebUnitTests('canvaskit', false); -} - -Future _runWebSkwasmUnitTests() { - return _runWebUnitTests('skwasm', true); -} - -Future _runWebUnitTests(String webRenderer, bool useWasm) async { - final Map subshards = {}; - - final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter')); - final Directory flutterPackageTestDirectory = Directory(path.join(flutterPackageDirectory.path, 'test')); - - final List allTests = flutterPackageTestDirectory - .listSync() - .whereType() - .expand((Directory directory) => directory - .listSync(recursive: true) - .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart')) - ) - .whereType() - .map((File file) => path.relative(file.path, from: flutterPackageDirectory.path)) - .where((String filePath) => !kWebTestFileKnownFailures[webRenderer]!.contains(path.split(filePath).join('/'))) - .toList() - // Finally we shuffle the list because we want the average cost per file to be uniformly - // distributed. If the list is not sorted then different shards and batches may have - // very different characteristics. - // We use a constant seed for repeatability. - ..shuffle(math.Random(0)); - - assert(webShardCount >= 1); - final int testsPerShard = (allTests.length / webShardCount).ceil(); - assert(testsPerShard * webShardCount >= allTests.length); - - // This for loop computes all but the last shard. - for (int index = 0; index < webShardCount - 1; index += 1) { - subshards['$index'] = () => runFlutterWebTest( - webRenderer, - flutterPackageDirectory.path, - allTests.sublist( - index * testsPerShard, - (index + 1) * testsPerShard, - ), - useWasm, - ); - } - - // The last shard also runs the flutter_web_plugins tests. - // - // We make sure the last shard ends in _last so it's easier to catch mismatches - // between `.cirrus.yml` and `test.dart`. - subshards['${webShardCount - 1}_last'] = () async { - await runFlutterWebTest( - webRenderer, - flutterPackageDirectory.path, - allTests.sublist( - (webShardCount - 1) * testsPerShard, - allTests.length, - ), - useWasm, - ); - await runFlutterWebTest( - webRenderer, - path.join(flutterRoot, 'packages', 'flutter_web_plugins'), - ['test'], - useWasm, - ); - await runFlutterWebTest( - webRenderer, - path.join(flutterRoot, 'packages', 'flutter_driver'), - [path.join('test', 'src', 'web_tests', 'web_extension_test.dart')], - useWasm, - ); - }; - - await selectSubshard(subshards); -} - - -Future runFlutterWebTest( - String webRenderer, - String workingDirectory, - List tests, - bool useWasm, -) async { - const LocalFileSystem fileSystem = LocalFileSystem(); - final String suffix = DateTime.now().microsecondsSinceEpoch.toString(); - final File metricFile = fileSystem.systemTempDirectory.childFile('metrics_$suffix.json'); - await runCommand( - flutter, - [ - 'test', - '--reporter=expanded', - '--file-reporter=json:${metricFile.path}', - '-v', - '--platform=chrome', - if (useWasm) '--wasm', - '--web-renderer=$webRenderer', - '--dart-define=DART_HHH_BOT=$_runningInDartHHHBot', - ...flutterTestArgs, - ...tests, - ], - workingDirectory: workingDirectory, - environment: { - 'FLUTTER_WEB': 'true', - }, - ); - // 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(); -} - - -// 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, -}) async { - int? cpus; - final String? cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml - if (cpuVariable != null) { - cpus = int.tryParse(cpuVariable, radix: 10); - if (cpus == null) { - foundError([ - '${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset', - 'Actual value: "$cpuVariable"', - ]); - return; - } - } else { - cpus = 2; // Don't default to 1, otherwise we won't catch race conditions. - } - // 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 (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, - ); - - 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 - 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(); -} - -/// 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; -} - -/// Parse (one-)index/total-named subshards from environment variable SUBSHARD -/// and equally distribute [tests] between them. -/// Subshard format 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 testsPerShard = (tests.length / total).ceil(); - final int start = (index - 1) * testsPerShard; - final int end = math.min(index * testsPerShard, tests.length); - - print('Selecting subshard $index of $total (tests ${start + 1}-$end of ${tests.length})'); - return tests.sublist(start, end); -} - -Future runShardRunnerIndexOfTotalSubshard(List tests) async { - final List sublist = _selectIndexOfTotalSubshard(tests); - for (final ShardRunner test in sublist) { - await test(); - } -} - -Future selectShard(Map shards) => _runFromList(shards, kShardKey, 'shard', 0); -Future selectSubshard(Map subshards) => _runFromList(subshards, kSubshardKey, 'subshard', 1); - -const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME'; - -Future _runFromList(Map items, String key, String name, int positionInTaskName) async { - String? item = Platform.environment[key]; - if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) { - final List parts = Platform.environment[CIRRUS_TASK_NAME]!.split('-'); - assert(positionInTaskName < parts.length); - item = parts[positionInTaskName]; - } - 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]!(); - } -} diff --git a/dev/bots/test/analyze-test-input/root/packages/foo/bad_repository_links.dart b/dev/bots/test/analyze-test-input/root/packages/foo/bad_repository_links.dart new file mode 100644 index 000000000000..38417c183120 --- /dev/null +++ b/dev/bots/test/analyze-test-input/root/packages/foo/bad_repository_links.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. + +// Check out https://android.googlesource.com/+/master/file1 +// Check out https://chromium.googlesource.com/+/master/file1 +// Check out https://cs.opensource.google.com/+/master/file1 +// Check out https://dart.googlesource.com/+/master/file1 +// Check out https://flutter.googlesource.com/+/master/file1 +// Check out https://source.chromium.org/+/master/file1 +// Check out https://github.com/flutter/flutter/tree/master/file1 +// Check out https://raw.githubusercontent.com/flutter/flutter/blob/master/file1 diff --git a/dev/bots/test/analyze_test.dart b/dev/bots/test/analyze_test.dart index 5941ab7524b2..1ed50e053ed9 100644 --- a/dev/bots/test/analyze_test.dart +++ b/dev/bots/test/analyze_test.dart @@ -147,6 +147,31 @@ void main() { ); }); + 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, @@ -310,7 +335,7 @@ void main() { expect(result, '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' '$lines\n' - '║ See: https://github.com/flutter/flutter/blob/master/dev/tools/gen_defaults to update the token template files.\n' + '║ See: https://github.com/flutter/flutter/blob/main/dev/tools/gen_defaults to update the token template files.\n' '╚═══════════════════════════════════════════════════════════════════════════════\n' ); }); diff --git a/dev/bots/test/test_test.dart b/dev/bots/test/test_test.dart index 77f16e91b7aa..e620de8f3ae2 100644 --- a/dev/bots/test/test_test.dart +++ b/dev/bots/test/test_test.dart @@ -10,7 +10,7 @@ import 'package:path/path.dart' as path; import 'package:process/process.dart'; import '../suite_runners/run_flutter_packages_tests.dart'; -import '../test.dart'; +import '../utils.dart'; import 'common.dart'; /// Fails a test if the exit code of `result` is not the expected value. This diff --git a/dev/bots/utils.dart b/dev/bots/utils.dart index f302d6285373..61b523ed54b0 100644 --- a/dev/bots/utils.dart +++ b/dev/bots/utils.dart @@ -3,6 +3,7 @@ // 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; @@ -10,15 +11,30 @@ import 'dart:math' as math; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.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 @@ -30,6 +46,31 @@ final String gray = hasColor ? '\x1B[30m' : ''; // subtle decorative items (usua 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'; +const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME'; + +/// 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 = []; + + const int kESC = 0x1B; const int kOpenSquareBracket = 0x5B; const int kCSIParameterRangeStart = 0x30; @@ -39,7 +80,6 @@ const int kCSIIntermediateRangeEnd = 0x2F; const int kCSIFinalRangeStart = 0x40; const int kCSIFinalRangeEnd = 0x7E; - String get redLine { if (hasColor) { return '$red${'━' * stdout.terminalColumns}$reset'; @@ -260,3 +300,325 @@ Future _isPortAvailable(int port) async { String locationInFile(ResolvedUnitResult unit, AstNode node, String workingDirectory) { return '${path.relative(path.relative(unit.path, from: workingDirectory))}:${unit.lineInfo.getLocation(node.offset).lineNumber}'; } + +// 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, +}) async { + int? cpus; + final String? cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml + if (cpuVariable != null) { + cpus = int.tryParse(cpuVariable, radix: 10); + if (cpus == null) { + foundError([ + '${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset', + 'Actual value: "$cpuVariable"', + ]); + return; + } + } else { + cpus = 2; // Don't default to 1, otherwise we won't catch race conditions. + } + // 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 (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, + ); + + 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 + 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. +/// Subshard format 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 testsPerShard = (tests.length / total).ceil(); + final int start = (index - 1) * testsPerShard; + final int end = math.min(index * testsPerShard, tests.length); + + print('Selecting subshard $index of $total (tests ${start + 1}-$end of ${tests.length})'); + return tests.sublist(start, end); +} + +Future _runFromList(Map items, String key, String name, int positionInTaskName) async { + String? item = Platform.environment[key]; + if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) { + final List parts = Platform.environment[CIRRUS_TASK_NAME]!.split('-'); + assert(positionInTaskName < parts.length); + item = parts[positionInTaskName]; + } + 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]!(); + } +} + +/// 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/dev/conductor/core/bin/cli.dart b/dev/conductor/core/bin/cli.dart index 6c2025b47a27..f1394ca0719a 100644 --- a/dev/conductor/core/bin/cli.dart +++ b/dev/conductor/core/bin/cli.dart @@ -13,7 +13,7 @@ import 'package:file/local.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; -const String readmeUrl = 'https://github.com/flutter/flutter/tree/master/dev/conductor/README.md'; +const String readmeUrl = 'https://github.com/flutter/flutter/tree/main/dev/conductor/README.md'; Future main(List args) async { const FileSystem fileSystem = LocalFileSystem(); diff --git a/dev/conductor/core/lib/src/next.dart b/dev/conductor/core/lib/src/next.dart index 1b5669dcc0f8..5ab02f008209 100644 --- a/dev/conductor/core/lib/src/next.dart +++ b/dev/conductor/core/lib/src/next.dart @@ -93,10 +93,7 @@ class NextContext extends Context { ]; switch (state.currentPhase) { case pb.ReleasePhase.APPLY_ENGINE_CHERRYPICKS: - final Remote upstream = Remote( - name: RemoteName.upstream, - url: state.engine.upstream.url, - ); + final Remote upstream = Remote.upstream(state.engine.upstream.url); final EngineRepository engine = EngineRepository( checkouts, initialRef: state.engine.workingBranch, @@ -110,12 +107,10 @@ class NextContext extends Context { break; } - final List unappliedCherrypicks = []; - for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) { - if (!finishedStates.contains(cherrypick.state)) { - unappliedCherrypicks.add(cherrypick); - } - } + final List unappliedCherrypicks = [ + for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) + if (!finishedStates.contains(cherrypick.state)) cherrypick, + ]; if (unappliedCherrypicks.isEmpty) { stdio.printStatus('All engine cherrypicks have been auto-applied by the conductor.\n'); @@ -155,10 +150,7 @@ class NextContext extends Context { } } case pb.ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: - final Remote engineUpstreamRemote = Remote( - name: RemoteName.upstream, - url: state.engine.upstream.url, - ); + final Remote engineUpstreamRemote = Remote.upstream(state.engine.upstream.url); final EngineRepository engine = EngineRepository( checkouts, // We explicitly want to check out the merged version from upstream @@ -169,10 +161,7 @@ class NextContext extends Context { final String engineRevision = await engine.reverseParse('HEAD'); - final Remote upstream = Remote( - name: RemoteName.upstream, - url: state.framework.upstream.url, - ); + final Remote upstream = Remote.upstream(state.framework.upstream.url); final FrameworkRepository framework = FrameworkRepository( checkouts, initialRef: state.framework.workingBranch, @@ -206,12 +195,10 @@ class NextContext extends Context { ); } - final List unappliedCherrypicks = []; - for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) { - if (!finishedStates.contains(cherrypick.state)) { - unappliedCherrypicks.add(cherrypick); - } - } + final List unappliedCherrypicks = [ + for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) + if (!finishedStates.contains(cherrypick.state)) cherrypick, + ]; if (state.framework.cherrypicks.isEmpty) { stdio.printStatus( diff --git a/dev/conductor/core/lib/src/repository.dart b/dev/conductor/core/lib/src/repository.dart index 26d7514cc60b..64ba997870e4 100644 --- a/dev/conductor/core/lib/src/repository.dart +++ b/dev/conductor/core/lib/src/repository.dart @@ -183,15 +183,11 @@ abstract class Repository { workingDirectory: (await checkoutDirectory).path, ); - final List remoteBranches = []; - for (final String line in output.split('\n')) { - final RegExpMatch? match = _lsRemotePattern.firstMatch(line); - if (match != null) { - remoteBranches.add(match.group(1)!); - } - } - - return remoteBranches; + 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. @@ -524,8 +520,7 @@ class FrameworkRepository extends Repository { FrameworkRepository( this.checkouts, { super.name = 'framework', - super.upstreamRemote = const Remote( - name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream), + super.upstreamRemote = const Remote.upstream(FrameworkRepository.defaultUpstream), super.localUpstream, super.previousCheckoutLocation, String super.initialRef = FrameworkRepository.defaultBranch, @@ -557,10 +552,7 @@ class FrameworkRepository extends Repository { return FrameworkRepository( checkouts, name: name, - upstreamRemote: Remote( - name: RemoteName.upstream, - url: 'file://$upstreamPath/', - ), + upstreamRemote: Remote.upstream('file://$upstreamPath/'), previousCheckoutLocation: previousCheckoutLocation, initialRef: initialRef, ); @@ -585,9 +577,7 @@ class FrameworkRepository extends Repository { return FrameworkRepository( checkouts, name: cloneName, - upstreamRemote: Remote( - name: RemoteName.upstream, - url: 'file://${(await checkoutDirectory).path}/'), + upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), ); } @@ -741,10 +731,7 @@ class HostFrameworkRepository extends FrameworkRepository { }) : super( checkouts, name: name, - upstreamRemote: Remote( - name: RemoteName.upstream, - url: 'file://$upstreamPath/', - ), + upstreamRemote: Remote.upstream('file://$upstreamPath/'), localUpstream: false, ) { _checkoutDirectory = checkouts.fileSystem.directory(upstreamPath); @@ -799,8 +786,7 @@ class EngineRepository extends Repository { this.checkouts, { super.name = 'engine', String super.initialRef = EngineRepository.defaultBranch, - super.upstreamRemote = const Remote( - name: RemoteName.upstream, url: EngineRepository.defaultUpstream), + super.upstreamRemote = const Remote.upstream(EngineRepository.defaultUpstream), super.localUpstream, super.previousCheckoutLocation, super.mirrorRemote, @@ -851,9 +837,7 @@ class EngineRepository extends Repository { return EngineRepository( checkouts, name: cloneName, - upstreamRemote: Remote( - name: RemoteName.upstream, - url: 'file://${(await checkoutDirectory).path}/'), + upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), ); } } diff --git a/dev/conductor/core/lib/src/start.dart b/dev/conductor/core/lib/src/start.dart index f5de61b06d53..e692b668fd42 100644 --- a/dev/conductor/core/lib/src/start.dart +++ b/dev/conductor/core/lib/src/start.dart @@ -218,26 +218,14 @@ class StartContext extends Context { engine = EngineRepository( checkouts, initialRef: 'upstream/$candidateBranch', - upstreamRemote: Remote( - name: RemoteName.upstream, - url: engineUpstream, - ), - mirrorRemote: Remote( - name: RemoteName.mirror, - url: engineMirror, - ), + upstreamRemote: Remote.upstream(engineUpstream), + mirrorRemote: Remote.mirror(engineMirror), ), framework = FrameworkRepository( checkouts, initialRef: 'upstream/$candidateBranch', - upstreamRemote: Remote( - name: RemoteName.upstream, - url: frameworkUpstream, - ), - mirrorRemote: Remote( - name: RemoteName.mirror, - url: frameworkMirror, - ), + upstreamRemote: Remote.upstream(frameworkUpstream), + mirrorRemote: Remote.mirror(frameworkMirror), ); final String candidateBranch; diff --git a/dev/conductor/core/pubspec.yaml b/dev/conductor/core/pubspec.yaml index c5069037dde2..2c0d1a95c89b 100644 --- a/dev/conductor/core/pubspec.yaml +++ b/dev/conductor/core/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: args: 2.5.0 http: 0.13.6 intl: 0.19.0 - meta: 1.12.0 + meta: 1.14.0 path: 1.9.0 process: 5.0.2 protobuf: 3.1.0 @@ -30,15 +30,15 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 - test_api: 0.7.0 + test: 1.25.4 + test_api: 0.7.1 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -58,12 +58,12 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: a810 +# PUBSPEC CHECKSUM: 2f17 diff --git a/dev/conductor/core/test/next_test.dart b/dev/conductor/core/test/next_test.dart index 803b3ec42736..82933a474a63 100644 --- a/dev/conductor/core/test/next_test.dart +++ b/dev/conductor/core/test/next_test.dart @@ -1002,7 +1002,7 @@ class _TestRepository extends Repository { name: name, requiredLocalBranches: [], stdio: checkouts.stdio, - upstreamRemote: const Remote(name: RemoteName.upstream, url: 'git@github.com:upstream/repo.git'), + upstreamRemote: const Remote.upstream('git@github.com:upstream/repo.git'), ); @override diff --git a/dev/conductor/core/test/packages_autoroller_test.dart b/dev/conductor/core/test/packages_autoroller_test.dart index d320f731a491..19d96d817106 100644 --- a/dev/conductor/core/test/packages_autoroller_test.dart +++ b/dev/conductor/core/test/packages_autoroller_test.dart @@ -50,10 +50,7 @@ void main() { ); framework = FrameworkRepository( checkouts, - mirrorRemote: const Remote( - name: RemoteName.mirror, - url: mirrorUrl, - ), + mirrorRemote: const Remote.mirror(mirrorUrl), ); autoroller = PackageAutoroller( diff --git a/dev/customer_testing/ci.bat b/dev/customer_testing/ci.bat index 8afdafbe61b2..56cf7db139c0 100644 --- a/dev/customer_testing/ci.bat +++ b/dev/customer_testing/ci.bat @@ -6,7 +6,7 @@ REM found in the LICENSE file. REM This should match the ci.sh file in this directory. REM This is called from the LUCI recipes: -REM https://flutter.googlesource.com/recipes/+/refs/heads/master/recipe_modules/adhoc_validation/resources/customer_testing.bat +REM https://github.com/flutter/flutter/blob/main/dev/bots/suite_runners/run_customer_testing_tests.dart ECHO. ECHO Updating pub packages... diff --git a/dev/customer_testing/ci.sh b/dev/customer_testing/ci.sh index 3bc3d3ab86df..dbc52e9fca47 100755 --- a/dev/customer_testing/ci.sh +++ b/dev/customer_testing/ci.sh @@ -6,7 +6,7 @@ # This should match the ci.bat file in this directory. # This is called from .cirrus.yml and the LUCI recipes: -# https://flutter.googlesource.com/recipes/+/refs/heads/master/recipe_modules/adhoc_validation/resources/customer_testing.sh +# https://github.com/flutter/flutter/blob/main/dev/bots/suite_runners/run_customer_testing_tests.dart set -ex diff --git a/dev/customer_testing/pubspec.yaml b/dev/customer_testing/pubspec.yaml index c912735accc1..10a8ee2ace71 100644 --- a/dev/customer_testing/pubspec.yaml +++ b/dev/customer_testing/pubspec.yaml @@ -8,7 +8,7 @@ dependencies: args: 2.5.0 path: 1.9.0 glob: 2.1.2 - meta: 1.12.0 + meta: 1.14.0 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -18,7 +18,7 @@ dependencies: term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -26,7 +26,7 @@ dev_dependencies: convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,14 +46,14 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 075b +# PUBSPEC CHECKSUM: 6562 diff --git a/dev/customer_testing/run_tests.dart b/dev/customer_testing/run_tests.dart index cb3f42141862..276402b2efed 100644 --- a/dev/customer_testing/run_tests.dart +++ b/dev/customer_testing/run_tests.dart @@ -60,7 +60,7 @@ Future run(List arguments) async { 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/master/registry/template.test'); + print(' https://github.com/flutter/tests/blob/main/registry/template.test'); print(''); print(argParser.usage); print(''); diff --git a/dev/devicelab/README.md b/dev/devicelab/README.md index 5781861339cd..3bf02a086f08 100644 --- a/dev/devicelab/README.md +++ b/dev/devicelab/README.md @@ -217,7 +217,7 @@ _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/master/.ci.yaml) + [.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 @@ -237,7 +237,7 @@ 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/master/dev/devicelab/lib/tasks/build_test_task.dart) +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` diff --git a/dev/devicelab/bin/tasks/hello_world_impeller.dart b/dev/devicelab/bin/tasks/hello_world_impeller.dart index e67a25b48e6d..d094882fdaad 100644 --- a/dev/devicelab/bin/tasks/hello_world_impeller.dart +++ b/dev/devicelab/bin/tasks/hello_world_impeller.dart @@ -54,6 +54,7 @@ Future run() async { 'run', options: [ '--enable-impeller', + '--enable-vulkan-validation', '-d', device.deviceId, ], diff --git a/dev/devicelab/lib/framework/devices.dart b/dev/devicelab/lib/framework/devices.dart index 98e3fa491002..c73247620c96 100644 --- a/dev/devicelab/lib/framework/devices.dart +++ b/dev/devicelab/lib/framework/devices.dart @@ -649,7 +649,7 @@ class AndroidDevice extends Device { /// Retrieves device's wakefulness state. /// - /// See: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/PowerManagerInternal.java + /// See: https://android.googlesource.com/platform/frameworks/base/+/main/core/java/android/os/PowerManagerInternal.java Future _getWakefulness() async { final String powerInfo = await shellEval('dumpsys', ['power']); // A motoG4 phone returns `mWakefulness=Awake`. diff --git a/dev/devicelab/lib/tasks/microbenchmarks.dart b/dev/devicelab/lib/tasks/microbenchmarks.dart index 8b7a659a3454..aa38ab9a575c 100644 --- a/dev/devicelab/lib/tasks/microbenchmarks.dart +++ b/dev/devicelab/lib/tasks/microbenchmarks.dart @@ -39,8 +39,8 @@ TaskFunction createMicrobenchmarkTask({ if (enableImpeller != null && !enableImpeller) '--no-enable-impeller', '-d', device.deviceId, + benchmarkPath, ]; - options.add(benchmarkPath); return startFlutter( 'run', options: options, diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 45a81b3140f1..cf3ff2af2a7c 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: file: 7.0.0 http: 0.13.6 logging: 1.2.0 - meta: 1.12.0 + meta: 1.14.0 metrics_center: 1.0.13 path: 1.9.0 platform: 3.1.4 @@ -20,7 +20,7 @@ dependencies: shelf: 1.4.1 shelf_static: 1.1.2 stack_trace: 1.11.1 - vm_service: 14.2.0 + vm_service: 14.2.1 web: 0.5.1 webkit_inspection_protocol: 1.2.1 xml: 6.5.0 @@ -49,13 +49,13 @@ dependencies: yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,9 +68,9 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 406f +# PUBSPEC CHECKSUM: 8376 diff --git a/dev/forbidden_from_release_tests/pubspec.yaml b/dev/forbidden_from_release_tests/pubspec.yaml index 4de7991da289..39f26aee2583 100644 --- a/dev/forbidden_from_release_tests/pubspec.yaml +++ b/dev/forbidden_from_release_tests/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: vm_snapshot_analysis: 0.7.6 collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b68d +# PUBSPEC CHECKSUM: 228f diff --git a/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml b/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml index d6ed7ddac44a..513f0201ee0b 100644 --- a/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml +++ b/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml @@ -13,10 +13,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 9bc0 +# PUBSPEC CHECKSUM: edc2 diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml index 8100273da9ec..4144fe9725e5 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml +++ b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml @@ -23,12 +23,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -54,8 +54,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -95,4 +95,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: e006 +# PUBSPEC CHECKSUM: ba0c diff --git a/dev/integration_tests/android_semantics_testing/lib/src/common.dart b/dev/integration_tests/android_semantics_testing/lib/src/common.dart index fa9e3f5091f9..4af33282ef47 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/common.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/common.dart @@ -154,14 +154,11 @@ class AndroidSemanticsNode { if (actions == null) { return const []; } - final List convertedActions = []; - for (final int id in actions) { - final AndroidSemanticsAction? action = AndroidSemanticsAction.deserialize(id); - if (action != null) { - convertedActions.add(action); - } - } - return convertedActions; + return [ + for (final int id in actions) + if (AndroidSemanticsAction.deserialize(id) case final AndroidSemanticsAction action) + action, + ]; } @override diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index 621cd4bce086..21624eb2ab91 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter_test: sdk: flutter pub_semver: 2.1.4 - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -26,7 +26,7 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -38,7 +38,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -55,11 +55,11 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -69,4 +69,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 870b +# PUBSPEC CHECKSUM: d912 diff --git a/dev/integration_tests/android_verified_input/pubspec.yaml b/dev/integration_tests/android_verified_input/pubspec.yaml index 3aa3717fc346..b75f61f01cf6 100644 --- a/dev/integration_tests/android_verified_input/pubspec.yaml +++ b/dev/integration_tests/android_verified_input/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,15 +32,15 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,7 +50,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,7 +71,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,4 +79,4 @@ dev_dependencies: webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml index 118f41fc3dc4..bce9a14d5ec7 100644 --- a/dev/integration_tests/android_views/pubspec.yaml +++ b/dev/integration_tests/android_views/pubspec.yaml @@ -29,7 +29,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_foundation: 2.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_linux: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -43,9 +43,9 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" win32: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xdg_directories: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -53,7 +53,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,7 +63,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -84,7 +84,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -95,4 +95,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3552 +# PUBSPEC CHECKSUM: 1e59 diff --git a/dev/integration_tests/channels/macos/Runner/AppDelegate.swift b/dev/integration_tests/channels/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/integration_tests/channels/macos/Runner/AppDelegate.swift +++ b/dev/integration_tests/channels/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/integration_tests/channels/pubspec.yaml b/dev/integration_tests/channels/pubspec.yaml index 898d6c7981a5..8192c73c28e6 100644 --- a/dev/integration_tests/channels/pubspec.yaml +++ b/dev/integration_tests/channels/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -38,11 +38,11 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 8593 +# PUBSPEC CHECKSUM: 1997 diff --git a/dev/integration_tests/deferred_components_test/pubspec.yaml b/dev/integration_tests/deferred_components_test/pubspec.yaml index e62480e5c770..0ffc8b608dd0 100644 --- a/dev/integration_tests/deferred_components_test/pubspec.yaml +++ b/dev/integration_tests/deferred_components_test/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -26,15 +26,15 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -44,7 +44,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -83,4 +83,4 @@ flutter: assets: - customassets/flutter_logo.png -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/dev/integration_tests/deferred_components_test/run_release_test.sh b/dev/integration_tests/deferred_components_test/run_release_test.sh index 2f2c7241f029..46bf30db9171 100755 --- a/dev/integration_tests/deferred_components_test/run_release_test.sh +++ b/dev/integration_tests/deferred_components_test/run_release_test.sh @@ -9,7 +9,7 @@ # # In CI, this script currently depends on a modified version of bundletool because # ddmlib which bundletool depends on does not yet support detecting QEMU emulator device -# density system properties. See https://android.googlesource.com/platform/tools/base/+/refs/heads/master/ddmlib/src/main/java/com/android/ddmlib/IDevice.java#46 +# density system properties. See https://android.googlesource.com/platform/tools/base/+/refs/heads/main/ddmlib/src/main/java/com/android/ddmlib/IDevice.java#46 # # The modified bundletool which waives the density requirement is at: # https://chrome-infra-packages.appspot.com/p/flutter/android/bundletool/+/vFt1jA0cUeZLmUCVR5NG2JVB-SgJ18GH_pVYKMOlfUIC diff --git a/dev/integration_tests/external_textures/pubspec.yaml b/dev/integration_tests/external_textures/pubspec.yaml index eeea44a097a1..477bfb0b336e 100644 --- a/dev/integration_tests/external_textures/pubspec.yaml +++ b/dev/integration_tests/external_textures/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -22,7 +22,7 @@ dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,11 +50,11 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,4 +65,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4d8c +# PUBSPEC CHECKSUM: 4593 diff --git a/dev/integration_tests/flavors/macos/Runner/AppDelegate.swift b/dev/integration_tests/flavors/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/integration_tests/flavors/macos/Runner/AppDelegate.swift +++ b/dev/integration_tests/flavors/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/integration_tests/flavors/pubspec.yaml b/dev/integration_tests/flavors/pubspec.yaml index e2ec00e9325f..cea470080d03 100644 --- a/dev/integration_tests/flavors/pubspec.yaml +++ b/dev/integration_tests/flavors/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter integration_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -24,7 +24,7 @@ dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,7 +33,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -52,11 +52,11 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -85,4 +85,4 @@ flutter: flavors: - free -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/dev/integration_tests/flutter_gallery/lib/demo/calculator/logic.dart b/dev/integration_tests/flutter_gallery/lib/demo/calculator/logic.dart index ab409254339e..a90c12869b3a 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/calculator/logic.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/calculator/logic.dart @@ -121,10 +121,8 @@ class CalcExpression { : this([], ExpressionState.Start); CalcExpression.result(FloatToken result) - : _list = [], - state = ExpressionState.Result { - _list.add(result); - } + : _list = [result], + state = ExpressionState.Result; /// The tokens comprising the expression. final List _list; diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart index a68c2a194c9b..a063c7cfe4dc 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart @@ -48,7 +48,7 @@ class CupertinoNavigationDemo extends StatelessWidget { @override Widget build(BuildContext context) { - return PopScope( + return PopScope( // Prevent swipe popping of this page. Use explicit exit buttons only. canPop: false, child: DefaultTextStyle( diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart index 81ee4dacd864..140211387c7c 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart @@ -110,7 +110,7 @@ class FullScreenDialogDemoState extends State { bool _hasName = false; late String _eventName; - Future _handlePopInvoked(bool didPop) async { + Future _handlePopInvoked(bool didPop, Object? result) async { if (didPop) { return; } @@ -175,7 +175,7 @@ class FullScreenDialogDemoState extends State { ), body: Form( canPop: !_saveNeeded && !_hasLocation && !_hasName, - onPopInvoked: _handlePopInvoked, + onPopInvokedWithResult: _handlePopInvoked, child: Scrollbar( child: ListView( primary: true, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart index c6f644ee74cd..47811d81c51e 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart @@ -143,7 +143,7 @@ class TextFormFieldDemoState extends State { return null; } - Future _handlePopInvoked(bool didPop) async { + Future _handlePopInvoked(bool didPop, Object? result) async { if (didPop) { return; } @@ -192,7 +192,7 @@ class TextFormFieldDemoState extends State { key: _formKey, autovalidateMode: _autovalidateMode, canPop: _formKey.currentState == null || !_formWasEdited || _formKey.currentState!.validate(), - onPopInvoked: _handlePopInvoked, + onPopInvokedWithResult: _handlePopInvoked, child: Scrollbar( child: SingleChildScrollView( primary: true, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart b/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart index 5e7a95166859..0dfbcbe975de 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart @@ -355,7 +355,7 @@ class ExpandingBottomSheetState extends State with TickerP // Closes the cart if the cart is open, otherwise exits the app (this should // only be relevant for Android). - void _handlePopInvoked(bool didPop) { + void _handlePopInvoked(bool didPop, Object? result) { if (didPop) { return; } @@ -370,9 +370,9 @@ class ExpandingBottomSheetState extends State with TickerP duration: const Duration(milliseconds: 225), curve: Curves.easeInOut, alignment: FractionalOffset.topLeft, - child: PopScope( + child: PopScope( canPop: !_isOpen, - onPopInvoked: _handlePopInvoked, + onPopInvokedWithResult: _handlePopInvoked, child: AnimatedBuilder( animation: widget.hideController, builder: _buildSlideAnimation, diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/home.dart b/dev/integration_tests/flutter_gallery/lib/gallery/home.dart index a6ceab8850b0..fc7c4d800587 100644 --- a/dev/integration_tests/flutter_gallery/lib/gallery/home.dart +++ b/dev/integration_tests/flutter_gallery/lib/gallery/home.dart @@ -326,9 +326,9 @@ class _GalleryHomeState extends State with SingleTickerProviderStat backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor, body: SafeArea( bottom: false, - child: PopScope( + child: PopScope( canPop: _category == null, - onPopInvoked: (bool didPop) { + onPopInvokedWithResult: (bool didPop, Object? result) { if (didPop) { return; } diff --git a/dev/integration_tests/flutter_gallery/macos/Runner/AppDelegate.swift b/dev/integration_tests/flutter_gallery/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/integration_tests/flutter_gallery/macos/Runner/AppDelegate.swift +++ b/dev/integration_tests/flutter_gallery/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml index 4c9bfecc9244..a0082133e967 100644 --- a/dev/integration_tests/flutter_gallery/pubspec.yaml +++ b/dev/integration_tests/flutter_gallery/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: connectivity: 3.0.6 string_scanner: 1.2.0 url_launcher: 6.2.6 - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 video_player: 2.8.6 scoped_model: 2.0.0 shrine_images: 2.0.2 @@ -30,7 +30,7 @@ dependencies: device_info_platform_interface: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.15.4 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -40,10 +40,10 @@ dependencies: url_launcher_linux: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_macos: 3.1.0 # 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.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_web: 2.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_windows: 3.1.1 # 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.4.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + video_player_android: 2.4.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" video_player_avfoundation: 2.5.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" video_player_platform_interface: 6.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" video_player_web: 2.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -56,7 +56,7 @@ dev_dependencies: sdk: flutter flutter_goldens: sdk: flutter - test: 1.25.2 + test: 1.25.4 integration_test: sdk: flutter @@ -70,7 +70,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -97,10 +97,10 @@ dev_dependencies: stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -277,4 +277,4 @@ flutter: - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf -# PUBSPEC CHECKSUM: 27c9 +# PUBSPEC CHECKSUM: 45d4 diff --git a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml index a92859d2af5a..ed331273c165 100644 --- a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml +++ b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: camera: 0.10.5+9 camera_android: 0.10.8+17 - camera_avfoundation: 0.9.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + camera_avfoundation: 0.9.15+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" camera_platform_interface: 2.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" camera_web: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -18,7 +18,7 @@ dependencies: cross_file: 0.3.4+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter_plugin_android_lifecycle: 2.0.19 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # 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" stream_transform: 2.1.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" @@ -27,4 +27,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: ab38 +# PUBSPEC CHECKSUM: b799 diff --git a/dev/integration_tests/hybrid_android_views/pubspec.yaml b/dev/integration_tests/hybrid_android_views/pubspec.yaml index 5e740c1e629b..52ef2957099d 100644 --- a/dev/integration_tests/hybrid_android_views/pubspec.yaml +++ b/dev/integration_tests/hybrid_android_views/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_android: 2.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_foundation: 2.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -41,9 +41,9 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" win32: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xdg_directories: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -51,7 +51,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -61,7 +61,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -82,7 +82,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,4 +93,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3552 +# PUBSPEC CHECKSUM: 1e59 diff --git a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml index d2ef31117458..a2b59edd3a6e 100644 --- a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml +++ b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml @@ -22,12 +22,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -51,8 +51,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: # The following line ensures that the Material Icons font is @@ -102,4 +102,4 @@ flutter: androidPackage: com.example.iosadd2appflutter iosBundleIdentifier: com.example.iosAdd2appFlutter -# PUBSPEC CHECKSUM: e006 +# PUBSPEC CHECKSUM: ba0c diff --git a/dev/integration_tests/ios_app_with_extensions/ios/Runner/AppDelegate.swift b/dev/integration_tests/ios_app_with_extensions/ios/Runner/AppDelegate.swift index 36e03f7fbb9b..58f3ea9b9299 100644 --- a/dev/integration_tests/ios_app_with_extensions/ios/Runner/AppDelegate.swift +++ b/dev/integration_tests/ios_app_with_extensions/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml index 320d0354843d..ebcbc28d489b 100644 --- a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml +++ b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: collection: 1.18.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" device_info_platform_interface: 2.0.1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # 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" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -53,8 +53,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -94,4 +94,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: 998e +# PUBSPEC CHECKSUM: ec92 diff --git a/dev/integration_tests/ios_host_app_swift/Host/AppDelegate.swift b/dev/integration_tests/ios_host_app_swift/Host/AppDelegate.swift index 51ec64fcc2d2..e8cb4d2e958b 100644 --- a/dev/integration_tests/ios_host_app_swift/Host/AppDelegate.swift +++ b/dev/integration_tests/ios_host_app_swift/Host/AppDelegate.swift @@ -4,7 +4,7 @@ import UIKit -@UIApplicationMain +@main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) diff --git a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml index 4cf22fcadb40..5a94c2583f51 100644 --- a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml +++ b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -26,15 +26,15 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -44,7 +44,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +65,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +80,4 @@ flutter: # the material Icons class. uses-material-design: true -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/dev/integration_tests/new_gallery/lib/demos/cupertino/cupertino_search_text_field_demo.dart b/dev/integration_tests/new_gallery/lib/demos/cupertino/cupertino_search_text_field_demo.dart index 611b749c02e9..800af1668d5b 100644 --- a/dev/integration_tests/new_gallery/lib/demos/cupertino/cupertino_search_text_field_demo.dart +++ b/dev/integration_tests/new_gallery/lib/demos/cupertino/cupertino_search_text_field_demo.dart @@ -84,15 +84,11 @@ class _CupertinoSearchTextFieldDemoState Widget _buildPlatformList() { if (_searchPlatform.isNotEmpty) { - final List tempList = []; - for (int i = 0; i < filteredPlatforms.length; i++) { - if (filteredPlatforms[i] - .toLowerCase() - .contains(_searchPlatform.toLowerCase())) { - tempList.add(filteredPlatforms[i]); - } - } - filteredPlatforms = tempList; + final String search = _searchPlatform.toLowerCase(); + filteredPlatforms = [ + for (final String platform in filteredPlatforms) + if (platform.toLowerCase().contains(search)) platform + ]; } return ListView.builder( itemCount: filteredPlatforms.length, diff --git a/dev/integration_tests/new_gallery/lib/demos/material/data_table_demo.dart b/dev/integration_tests/new_gallery/lib/demos/material/data_table_demo.dart index d88ca630c430..ad4c46e6de30 100644 --- a/dev/integration_tests/new_gallery/lib/demos/material/data_table_demo.dart +++ b/dev/integration_tests/new_gallery/lib/demos/material/data_table_demo.dart @@ -26,14 +26,10 @@ class _RestorableDessertSelections extends RestorableProperty> { /// Takes a list of [_Dessert]s and saves the row indices of selected rows /// into a [Set]. void setDessertSelections(List<_Dessert> desserts) { - final Set updatedSet = {}; - for (int i = 0; i < desserts.length; i += 1) { - final _Dessert dessert = desserts[i]; - if (dessert.selected) { - updatedSet.add(i); - } - } - _dessertSelections = updatedSet; + _dessertSelections = { + for (final (int i, _Dessert dessert) in desserts.indexed) + if (dessert.selected) i, + }; notifyListeners(); } diff --git a/dev/integration_tests/new_gallery/lib/studies/reply/waterfall_notched_rectangle.dart b/dev/integration_tests/new_gallery/lib/studies/reply/waterfall_notched_rectangle.dart index 92b3214d483d..fda6f898db3d 100644 --- a/dev/integration_tests/new_gallery/lib/studies/reply/waterfall_notched_rectangle.dart +++ b/dev/integration_tests/new_gallery/lib/studies/reply/waterfall_notched_rectangle.dart @@ -46,7 +46,7 @@ class WaterfallNotchedRectangle extends NotchedShape { // A detailed explanation and the derivation of the formulas below is // available at: https://goo.gl/Ufzrqn - // s1, s2 are the two knobs controlling the behavior of the bezzier curve. + // s1, s2 are the two knobs controlling the behavior of the bezier curve. const double s1 = 21.0; const double s2 = 6.0; diff --git a/dev/integration_tests/new_gallery/pubspec.yaml b/dev/integration_tests/new_gallery/pubspec.yaml index 34491e3c1ada..843b3d8cbd06 100644 --- a/dev/integration_tests/new_gallery/pubspec.yaml +++ b/dev/integration_tests/new_gallery/pubspec.yaml @@ -15,14 +15,14 @@ dependencies: adaptive_breakpoints: 0.1.7 animations: 2.0.11 collection: 1.18.0 - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 dual_screen: 1.0.4 flutter_gallery_assets: 1.0.2 flutter_localized_locales: 2.0.5 flutter_staggered_grid_view: 0.7.0 google_fonts: 4.0.4 intl: 0.19.0 - meta: 1.12.0 + meta: 1.14.0 provider: 6.1.2 rally_assets: 3.0.1 scoped_model: 2.0.0 @@ -57,7 +57,7 @@ dependencies: url_launcher_linux: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_macos: 3.1.0 # 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.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_web: 2.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_windows: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" win32: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,7 +68,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -78,7 +78,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -102,9 +102,9 @@ dev_dependencies: stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -312,4 +312,4 @@ flutter: fonts: - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf -# PUBSPEC CHECKSUM: 3c63 +# PUBSPEC CHECKSUM: fd6d diff --git a/dev/integration_tests/new_gallery/test_driver/transitions_perf_test.dart b/dev/integration_tests/new_gallery/test_driver/transitions_perf_test.dart index 0fd87013be16..d62371ebb08a 100644 --- a/dev/integration_tests/new_gallery/test_driver/transitions_perf_test.dart +++ b/dev/integration_tests/new_gallery/test_driver/transitions_perf_test.dart @@ -250,7 +250,7 @@ void main([List args = const []]) { await driver.requestData('isTestingReplyOnly') == 'true'; if (args.contains('--with_semantics')) { - stdout.writeln('Enabeling semantics...'); + stdout.writeln('Enabling semantics...'); await driver.setSemantics(true); } diff --git a/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift b/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift index 36e03f7fbb9b..58f3ea9b9299 100644 --- a/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift +++ b/dev/integration_tests/non_nullable/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/dev/integration_tests/non_nullable/pubspec.yaml b/dev/integration_tests/non_nullable/pubspec.yaml index fd1de53fa221..962135ceb55a 100644 --- a/dev/integration_tests/non_nullable/pubspec.yaml +++ b/dev/integration_tests/non_nullable/pubspec.yaml @@ -10,12 +10,12 @@ environment: dependencies: flutter: sdk: flutter - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -36,10 +36,10 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: e006 +# PUBSPEC CHECKSUM: ba0c diff --git a/dev/integration_tests/platform_interaction/pubspec.yaml b/dev/integration_tests/platform_interaction/pubspec.yaml index d418a113676b..099b2c4fbf7b 100644 --- a/dev/integration_tests/platform_interaction/pubspec.yaml +++ b/dev/integration_tests/platform_interaction/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -22,7 +22,7 @@ dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,11 +50,11 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,4 +65,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4d8c +# PUBSPEC CHECKSUM: 4593 diff --git a/dev/integration_tests/release_smoke_test/pubspec.yaml b/dev/integration_tests/release_smoke_test/pubspec.yaml index 49b74027ed18..40082d6d11b5 100644 --- a/dev/integration_tests/release_smoke_test/pubspec.yaml +++ b/dev/integration_tests/release_smoke_test/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -33,7 +33,7 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/dev/integration_tests/spell_check/pubspec.yaml b/dev/integration_tests/spell_check/pubspec.yaml index c67c24e54a01..9959fd50295b 100644 --- a/dev/integration_tests/spell_check/pubspec.yaml +++ b/dev/integration_tests/spell_check/pubspec.yaml @@ -32,12 +32,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -67,8 +67,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -108,4 +108,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: e006 +# PUBSPEC CHECKSUM: ba0c diff --git a/dev/integration_tests/ui/macos/Runner/AppDelegate.swift b/dev/integration_tests/ui/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/integration_tests/ui/macos/Runner/AppDelegate.swift +++ b/dev/integration_tests/ui/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index 2501175b973c..247e59183ff9 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: sdk: flutter integration_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -24,7 +24,7 @@ dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,7 +33,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -52,10 +52,10 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -66,7 +66,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - test_api: 0.7.0 + test_api: 0.7.1 clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,4 +79,4 @@ flutter: assets: - assets/foo.png -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/dev/integration_tests/web/pubspec.yaml b/dev/integration_tests/web/pubspec.yaml index 5ec976467e00..9939e794fc85 100644 --- a/dev/integration_tests/web/pubspec.yaml +++ b/dev/integration_tests/web/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" -# PUBSPEC CHECKSUM: 194d +# PUBSPEC CHECKSUM: 934f diff --git a/dev/integration_tests/web_compile_tests/pubspec.yaml b/dev/integration_tests/web_compile_tests/pubspec.yaml index 1d40d5bf039e..cb6ac984b0c9 100644 --- a/dev/integration_tests/web_compile_tests/pubspec.yaml +++ b/dev/integration_tests/web_compile_tests/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" -# PUBSPEC CHECKSUM: 9bc0 +# PUBSPEC CHECKSUM: edc2 diff --git a/dev/integration_tests/web_e2e_tests/README.md b/dev/integration_tests/web_e2e_tests/README.md index f58efd854f71..ca93161c11ec 100644 --- a/dev/integration_tests/web_e2e_tests/README.md +++ b/dev/integration_tests/web_e2e_tests/README.md @@ -37,5 +37,5 @@ More resources: [1]: https://chromedriver.chromium.org/downloads [2]: https://flutter.dev/docs/development/tools/web-renderers -[3]: https://github.com/flutter/flutter/blob/master/dev/bots/test.dart +[3]: https://github.com/flutter/flutter/blob/main/dev/bots/test.dart [4]: https://flutter.dev/docs/testing/build-modes diff --git a/dev/integration_tests/web_e2e_tests/pubspec.yaml b/dev/integration_tests/web_e2e_tests/pubspec.yaml index 1bc83b7d4288..7ec86315d261 100644 --- a/dev/integration_tests/web_e2e_tests/pubspec.yaml +++ b/dev/integration_tests/web_e2e_tests/pubspec.yaml @@ -35,7 +35,7 @@ dependencies: leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -43,16 +43,16 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_goldens: sdk: flutter http: 0.13.6 - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,7 +60,7 @@ dev_dependencies: convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,11 +80,11 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 6e37 +# PUBSPEC CHECKSUM: e33e diff --git a/dev/integration_tests/wide_gamut_test/pubspec.yaml b/dev/integration_tests/wide_gamut_test/pubspec.yaml index 614e4735d888..aaa58bb5d754 100644 --- a/dev/integration_tests/wide_gamut_test/pubspec.yaml +++ b/dev/integration_tests/wide_gamut_test/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -37,10 +37,10 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/dev/integration_tests/windows_startup_test/pubspec.yaml b/dev/integration_tests/windows_startup_test/pubspec.yaml index 0f725b2344df..5db3a6560829 100644 --- a/dev/integration_tests/windows_startup_test/pubspec.yaml +++ b/dev/integration_tests/windows_startup_test/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -22,7 +22,7 @@ dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dependencies: logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,11 +50,11 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,4 +62,4 @@ dependencies: webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 4d8c +# PUBSPEC CHECKSUM: 4593 diff --git a/dev/manual_tests/ios/Runner/AppDelegate.swift b/dev/manual_tests/ios/Runner/AppDelegate.swift index 36e03f7fbb9b..58f3ea9b9299 100644 --- a/dev/manual_tests/ios/Runner/AppDelegate.swift +++ b/dev/manual_tests/ios/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Flutter import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/dev/manual_tests/macos/Runner/AppDelegate.swift b/dev/manual_tests/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/dev/manual_tests/macos/Runner/AppDelegate.swift +++ b/dev/manual_tests/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/dev/manual_tests/pubspec.yaml b/dev/manual_tests/pubspec.yaml index 1727608bd2ed..8d462ec698a9 100644 --- a/dev/manual_tests/pubspec.yaml +++ b/dev/manual_tests/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -31,10 +31,10 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/dev/snippets/config/README.md b/dev/snippets/config/README.md index d5539eeb56d5..87cc99db451b 100644 --- a/dev/snippets/config/README.md +++ b/dev/snippets/config/README.md @@ -4,4 +4,4 @@ The [snippets] tool uses the files in the `skeletons` directory to inject code blocks generated from `{@tool dartpad}`, `{@tool sample}`, and `{@tool snippet}` sections found in doc comments into the API docs. -[snippets]: https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets +[snippets]: https://github.com/flutter/assets-for-api-docs/tree/main/packages/snippets diff --git a/dev/tools/examples_smoke_test.dart b/dev/tools/examples_smoke_test.dart index 84660fda3873..8eff94d1b927 100644 --- a/dev/tools/examples_smoke_test.dart +++ b/dev/tools/examples_smoke_test.dart @@ -116,19 +116,18 @@ Future generateTest(Directory apiDir) async { }); // Collect the examples, and import them all as separate symbols. - final List imports = []; - imports.add('''import 'package:flutter/widgets.dart';'''); - imports.add('''import 'package:flutter/scheduler.dart';'''); - imports.add('''import 'package:flutter_test/flutter_test.dart';'''); - imports.add('''import 'package:integration_test/integration_test.dart';'''); - final List infoList = []; - for (final File example in examples) { - final ExampleInfo info = ExampleInfo(example, examplesLibDir); - infoList.add(info); - imports.add('''import 'package:flutter_api_samples/${info.importPath}' as ${info.importName};'''); - } - imports.sort(); + final List infoList = [ + for (final File example in examples) ExampleInfo(example, examplesLibDir), + ]; infoList.sort((ExampleInfo a, ExampleInfo b) => a.importPath.compareTo(b.importPath)); + final List imports = [ + "import 'package:flutter/widgets.dart';", + "import 'package:flutter/scheduler.dart';", + "import 'package:flutter_test/flutter_test.dart';", + "import 'package:integration_test/integration_test.dart';", + for (final ExampleInfo info in infoList) + "import 'package:flutter_api_samples/${info.importPath}' as ${info.importName};" + ]..sort(); final StringBuffer buffer = StringBuffer(); buffer.writeln('// Temporary generated file. Do not commit.'); diff --git a/dev/tools/gen_defaults/lib/segmented_button_template.dart b/dev/tools/gen_defaults/lib/segmented_button_template.dart index 4d1254ea109a..b23b8129cc1a 100644 --- a/dev/tools/gen_defaults/lib/segmented_button_template.dart +++ b/dev/tools/gen_defaults/lib/segmented_button_template.dart @@ -120,27 +120,27 @@ class _${blockName}DefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor){ + static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor, Color? overlayColor){ return MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return selectedColor?.withOpacity(0.08); + return (overlayColor ?? selectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } } else { if (states.contains(MaterialState.pressed)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return unselectedColor?.withOpacity(0.08); + return (overlayColor ?? unselectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } } return Colors.transparent; diff --git a/dev/tools/gen_defaults/pubspec.yaml b/dev/tools/gen_defaults/pubspec.yaml index 6330801b4aaf..eb249815b407 100644 --- a/dev/tools/gen_defaults/pubspec.yaml +++ b/dev/tools/gen_defaults/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: dev_dependencies: path: 1.9.0 - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -21,7 +21,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -29,7 +29,7 @@ dev_dependencies: js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.5 # 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.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,14 +46,14 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 075b +# PUBSPEC CHECKSUM: 6562 diff --git a/dev/tools/gen_keycodes/README.md b/dev/tools/gen_keycodes/README.md index f9ff1ad13820..99811c0dabd7 100644 --- a/dev/tools/gen_keycodes/README.md +++ b/dev/tools/gen_keycodes/README.md @@ -77,7 +77,7 @@ The planes are planned as follows: - **Plane 0x01**: The unprintable plane. This plane contains logical keys that are defined by the [Chromium key - list](https://chromium.googlesource.com/codesearch/chromium/src/+/refs/heads/master/ui/events/keycodes/dom/dom_key_data.inc) + list](https://chromium.googlesource.com/codesearch/chromium/src/+/refs/heads/main/ui/events/keycodes/dom/dom_key_data.inc) and do not generate Unicode characters. The value is defined as the macro value defined by the Chromium key list. Examples are CapsLock (0x105), ArrowUp (0x304), F1 (0x801), Hiragata (0x716), and TVPower (0xD4B). diff --git a/dev/tools/gen_keycodes/bin/gen_keycodes.dart b/dev/tools/gen_keycodes/bin/gen_keycodes.dart index e715bd1a0884..3dda5cebce7b 100644 --- a/dev/tools/gen_keycodes/bin/gen_keycodes.dart +++ b/dev/tools/gen_keycodes/bin/gen_keycodes.dart @@ -39,7 +39,7 @@ Future getChromiumKeys() async { /// Get contents of the file that contains the key codes in Android source. Future getAndroidKeyCodes() async { - final Uri keyCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h?format=TEXT'); + final Uri keyCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/native/+/main/include/android/keycodes.h?format=TEXT'); return utf8.decode(base64.decode(await http.read(keyCodesUri))); } @@ -55,7 +55,7 @@ Future getWindowsKeyCodes() async { /// common keyboards. Other than some special keyboards and game pads, this /// should be OK. Future getAndroidScanCodes() async { - final Uri scanCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/base/+/master/data/keyboards/Generic.kl?format=TEXT'); + final Uri scanCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/base/+/main/data/keyboards/Generic.kl?format=TEXT'); return utf8.decode(base64.decode(await http.read(scanCodesUri))); } diff --git a/dev/tools/gen_keycodes/pubspec.yaml b/dev/tools/gen_keycodes/pubspec.yaml index cc164d698c64..1d6eb1306216 100644 --- a/dev/tools/gen_keycodes/pubspec.yaml +++ b/dev/tools/gen_keycodes/pubspec.yaml @@ -7,7 +7,7 @@ environment: dependencies: args: 2.5.0 http: 0.13.6 - meta: 1.12.0 + meta: 1.14.0 path: 1.9.0 platform: 3.1.4 @@ -20,8 +20,8 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 - test_api: 0.7.0 + test: 1.25.4 + test_api: 0.7.1 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -30,7 +30,7 @@ dev_dependencies: coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,12 +50,12 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: fc56 +# PUBSPEC CHECKSUM: 615d diff --git a/dev/tools/pubspec.yaml b/dev/tools/pubspec.yaml index 2d40d694846c..ae654ce20c2b 100644 --- a/dev/tools/pubspec.yaml +++ b/dev/tools/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: args: 2.5.0 http: 0.13.6 intl: 0.19.0 - meta: 1.12.0 + meta: 1.14.0 path: 1.9.0 process: 5.0.2 pub_semver: 2.1.4 @@ -28,15 +28,15 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 - test_api: 0.7.0 + test: 1.25.4 + test_api: 0.7.1 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -55,11 +55,11 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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" -# PUBSPEC CHECKSUM: 1c6d +# PUBSPEC CHECKSUM: 8474 diff --git a/dev/tools/update_icons.dart b/dev/tools/update_icons.dart index 9d56f540ba95..1007f722407f 100644 --- a/dev/tools/update_icons.dart +++ b/dev/tools/update_icons.dart @@ -388,15 +388,10 @@ bool testIsSuperset(Map newCodepoints, Map oldCo @visibleForTesting bool testIsStable(Map newCodepoints, Map oldCodepoints) { final int oldCodepointsCount = oldCodepoints.length; - final List unstable = []; - - oldCodepoints.forEach((String key, String value) { - if (newCodepoints.containsKey(key)) { - if (value != newCodepoints[key]) { - unstable.add(key); - } - } - }); + final List unstable = [ + for (final MapEntry(:String key, :String value) in oldCodepoints.entries) + if (newCodepoints.containsKey(key) && value != newCodepoints[key]) key, + ]; if (unstable.isNotEmpty) { stderr.writeln('❌ out of $oldCodepointsCount existing codepoints, ${unstable.length} were unstable: $unstable'); diff --git a/dev/tools/vitool/pubspec.yaml b/dev/tools/vitool/pubspec.yaml index 42ea66b6ec3e..9efe9010fcc6 100644 --- a/dev/tools/vitool/pubspec.yaml +++ b/dev/tools/vitool/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 6.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: @@ -37,7 +37,7 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 3c06 +# PUBSPEC CHECKSUM: a70a diff --git a/dev/tracing_tests/pubspec.yaml b/dev/tracing_tests/pubspec.yaml index 2a3fc81bf7c2..6d1cd0dc2c08 100644 --- a/dev/tracing_tests/pubspec.yaml +++ b/dev/tracing_tests/pubspec.yaml @@ -8,12 +8,12 @@ dependencies: flutter: sdk: flutter - vm_service: 14.2.0 + vm_service: 14.2.1 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -34,6 +34,6 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/examples/api/.gitignore b/examples/api/.gitignore new file mode 100644 index 000000000000..ad536aadedae --- /dev/null +++ b/examples/api/.gitignore @@ -0,0 +1,3 @@ +# Unused platform specific files +android/ +ios/ \ No newline at end of file diff --git a/examples/api/android/.gitignore b/examples/api/android/.gitignore deleted file mode 100644 index 6f568019d3c6..000000000000 --- a/examples/api/android/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties -**/*.keystore -**/*.jks diff --git a/examples/api/android/app/build.gradle b/examples/api/android/app/build.gradle deleted file mode 100644 index 89af970f62fa..000000000000 --- a/examples/api/android/app/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -// 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 "dev.flutter.flutter-gradle-plugin" - id "kotlin-android" -} - -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' -} - -android { - namespace "dev.flutter.flutter_api_samples" - compileSdk flutter.compileSdkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - applicationId "dev.flutter.flutter_api_samples" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' - implementation 'com.google.android.material:material:1.5.0' -} diff --git a/examples/api/android/app/src/debug/AndroidManifest.xml b/examples/api/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 5dba98d65311..000000000000 --- a/examples/api/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/examples/api/android/app/src/main/AndroidManifest.xml b/examples/api/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index c861a135b357..000000000000 --- a/examples/api/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/api/android/app/src/main/kotlin/dev/flutter/flutter_api_samples/MainActivity.kt b/examples/api/android/app/src/main/kotlin/dev/flutter/flutter_api_samples/MainActivity.kt deleted file mode 100644 index 68e62645ab47..000000000000 --- a/examples/api/android/app/src/main/kotlin/dev/flutter/flutter_api_samples/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.flutter.flutter_api_samples - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/examples/api/android/app/src/main/res/color/fab_ripple_color.xml b/examples/api/android/app/src/main/res/color/fab_ripple_color.xml deleted file mode 100644 index ea2ccbdc35c9..000000000000 --- a/examples/api/android/app/src/main/res/color/fab_ripple_color.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/examples/api/android/app/src/main/res/drawable/ic_add_black_24dp.xml b/examples/api/android/app/src/main/res/drawable/ic_add_black_24dp.xml deleted file mode 100644 index f99c4c64f65c..000000000000 --- a/examples/api/android/app/src/main/res/drawable/ic_add_black_24dp.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/examples/api/android/app/src/main/res/drawable/launch_background.xml b/examples/api/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 8726260df6d1..000000000000 --- a/examples/api/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/examples/api/android/app/src/main/res/layout/flutter_view_layout.xml b/examples/api/android/app/src/main/res/layout/flutter_view_layout.xml deleted file mode 100644 index ba7e253a949f..000000000000 --- a/examples/api/android/app/src/main/res/layout/flutter_view_layout.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/api/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/api/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b09..000000000000 Binary files a/examples/api/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/examples/api/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/api/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a..000000000000 Binary files a/examples/api/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/examples/api/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/api/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391482be..000000000000 Binary files a/examples/api/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/api/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/api/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a..000000000000 Binary files a/examples/api/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/api/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/api/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb2..000000000000 Binary files a/examples/api/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/api/android/app/src/main/res/values/colors.xml b/examples/api/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 07f7af62e7fb..000000000000 --- a/examples/api/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - #9E9E9E - #FFFFFF - diff --git a/examples/api/android/app/src/main/res/values/dimens.xml b/examples/api/android/app/src/main/res/values/dimens.xml deleted file mode 100644 index 1078837b0526..000000000000 --- a/examples/api/android/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - 16dp - 6dp - 12dp - 17sp - 30sp - diff --git a/examples/api/android/app/src/main/res/values/strings.xml b/examples/api/android/app/src/main/res/values/strings.xml deleted file mode 100644 index ced96bc7f877..000000000000 --- a/examples/api/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - Flutter API Sample - Flutter API Sample - Flutter button tapped 0 times. - Android - diff --git a/examples/api/android/app/src/main/res/values/styles.xml b/examples/api/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 52a474457e8f..000000000000 --- a/examples/api/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/examples/api/android/app/src/profile/AndroidManifest.xml b/examples/api/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 5dba98d65311..000000000000 --- a/examples/api/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/examples/api/android/build.gradle b/examples/api/android/build.gradle deleted file mode 100644 index 3d0ab2780bf8..000000000000 --- a/examples/api/android/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -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/examples/api/android/gradle.properties b/examples/api/android/gradle.properties deleted file mode 100644 index 259717082164..000000000000 --- a/examples/api/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true diff --git a/examples/api/android/gradle/wrapper/gradle-wrapper.properties b/examples/api/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e1ca574ef017..000000000000 --- a/examples/api/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/examples/api/android/settings.gradle b/examples/api/android/settings.gradle deleted file mode 100644 index a9209c31b926..000000000000 --- a/examples/api/android/settings.gradle +++ /dev/null @@ -1,37 +0,0 @@ -// 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. - -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 "7.2.0" apply false - id "org.jetbrains.kotlin.android" version "1.6.10" apply false -} - -include ":app" diff --git a/examples/api/ios/.gitignore b/examples/api/ios/.gitignore deleted file mode 100644 index 151026b91bc9..000000000000 --- a/examples/api/ios/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/examples/api/ios/Flutter/AppFrameworkInfo.plist b/examples/api/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 7c5696400627..000000000000 --- a/examples/api/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 12.0 - - diff --git a/examples/api/ios/Flutter/Debug.xcconfig b/examples/api/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f3021..000000000000 --- a/examples/api/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/examples/api/ios/Flutter/Release.xcconfig b/examples/api/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe2000..000000000000 --- a/examples/api/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/examples/api/ios/Podfile b/examples/api/ios/Podfile deleted file mode 100644 index 279576f3884f..000000000000 --- a/examples/api/ios/Podfile +++ /dev/null @@ -1,41 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' - -# 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', '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 Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/examples/api/ios/Runner.xcodeproj/project.pbxproj b/examples/api/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 051d6d3edd1d..000000000000 --- a/examples/api/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,551 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2A933C5EDC11346D6057DD27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 864C6DD5813D75F8B897276C /* Pods_Runner.framework */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 864C6DD5813D75F8B897276C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B5D84224F8927D033E1EFF32 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - EB826BBD91D6CC0B1567DB17 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F57FB3C80C2AAB1B738761F7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2A933C5EDC11346D6057DD27 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 6DE3720F77894423C0FDB38B /* Frameworks */ = { - isa = PBXGroup; - children = ( - 864C6DD5813D75F8B897276C /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - FDDF5CF0D66BC5E2ACD4F8DA /* Pods */, - 6DE3720F77894423C0FDB38B /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - FDDF5CF0D66BC5E2ACD4F8DA /* Pods */ = { - isa = PBXGroup; - children = ( - F57FB3C80C2AAB1B738761F7 /* Pods-Runner.debug.xcconfig */, - B5D84224F8927D033E1EFF32 /* Pods-Runner.release.xcconfig */, - EB826BBD91D6CC0B1567DB17 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 3844CEFE24E0940030FA041C /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - F5C590E667F961A99E0AAC66 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3844CEFE24E0940030FA041C /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - F5C590E667F961A99E0AAC66 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.dartpadCurve2D0; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.dartpadCurve2D0; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.dartpadCurve2D0; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/api/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a6254f..000000000000 --- a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea15..000000000000 --- a/examples/api/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/examples/api/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/api/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index e67b2808af02..000000000000 --- a/examples/api/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/api/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/api/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c74e..000000000000 --- a/examples/api/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/api/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/api/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d68..000000000000 --- a/examples/api/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/examples/api/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/api/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5ea15..000000000000 --- a/examples/api/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/examples/api/ios/Runner/AppDelegate.swift b/examples/api/ios/Runner/AppDelegate.swift deleted file mode 100644 index d815fed684a8..000000000000 --- a/examples/api/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,17 +0,0 @@ -// 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 UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 13929fcb3b24..000000000000 --- a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "1x" - }, - { - "idiom" : "ipad", - "size" : "20x20", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118dda..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7edb8..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c285..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967d96..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5cee..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609df0..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png deleted file mode 100644 index f091b6b0bca8..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609df0..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a5a9..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39da7..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e14e2..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png deleted file mode 100644 index 5d2bad850374..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853602..000000000000 Binary files a/examples/api/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/examples/api/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/api/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c939..000000000000 --- a/examples/api/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/api/ios/Runner/Base.lproj/Main.storyboard b/examples/api/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516fb38..000000000000 --- a/examples/api/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/api/ios/Runner/Info.plist b/examples/api/ios/Runner/Info.plist deleted file mode 100644 index a48d067fdea1..000000000000 --- a/examples/api/ios/Runner/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Flutter API Sample - 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/examples/api/ios/Runner/Runner-Bridging-Header.h b/examples/api/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 95b7baf386d0..000000000000 --- a/examples/api/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1,5 +0,0 @@ -// 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/examples/api/lib/material/text_button/text_button.0.dart b/examples/api/lib/material/text_button/text_button.0.dart index dcf23a15c87b..181e553392f2 100644 --- a/examples/api/lib/material/text_button/text_button.0.dart +++ b/examples/api/lib/material/text_button/text_button.0.dart @@ -103,7 +103,7 @@ class _TextButtonExampleState extends State { // This gradient's appearance reflects the button's state. // Always return a gradient decoration so that AnimatedContainer - // can interpolorate in between. Used by TextButton #7. + // can interpolate in between. Used by TextButton #7. Decoration? statesToDecoration(Set states) { if (states.contains(MaterialState.pressed)) { return BoxDecoration( diff --git a/examples/api/lib/widgets/form/form.1.dart b/examples/api/lib/widgets/form/form.1.dart index e008f5aaa4d2..a7685152ab23 100644 --- a/examples/api/lib/widgets/form/form.1.dart +++ b/examples/api/lib/widgets/form/form.1.dart @@ -111,7 +111,7 @@ class _SaveableFormState extends State<_SaveableForm> { const SizedBox(height: 20.0), Form( canPop: !_isDirty, - onPopInvoked: (bool didPop) async { + onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) { return; } diff --git a/examples/api/lib/widgets/implicit_animations/animated_align.0.dart b/examples/api/lib/widgets/implicit_animations/animated_align.0.dart index 6f73cf704bd3..03c6a0c48b3d 100644 --- a/examples/api/lib/widgets/implicit_animations/animated_align.0.dart +++ b/examples/api/lib/widgets/implicit_animations/animated_align.0.dart @@ -11,19 +11,33 @@ void main() => runApp(const AnimatedAlignExampleApp()); class AnimatedAlignExampleApp extends StatelessWidget { const AnimatedAlignExampleApp({super.key}); + static const Duration duration = Duration(seconds: 1); + static const Curve curve = Curves.fastOutSlowIn; + @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('AnimatedAlign Sample')), - body: const AnimatedAlignExample(), + body: const AnimatedAlignExample( + duration: duration, + curve: curve, + ), ), ); } } class AnimatedAlignExample extends StatefulWidget { - const AnimatedAlignExample({super.key}); + const AnimatedAlignExample({ + required this.duration, + required this.curve, + super.key, + }); + + final Duration duration; + + final Curve curve; @override State createState() => _AnimatedAlignExampleState(); @@ -47,8 +61,8 @@ class _AnimatedAlignExampleState extends State { color: Colors.red, child: AnimatedAlign( alignment: selected ? Alignment.topRight : Alignment.bottomLeft, - duration: const Duration(seconds: 1), - curve: Curves.fastOutSlowIn, + duration: widget.duration, + curve: widget.curve, child: const FlutterLogo(size: 50.0), ), ), diff --git a/examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart b/examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart index bc0a2948cb3c..519b3aec5407 100644 --- a/examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart +++ b/examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart @@ -11,13 +11,19 @@ void main() => runApp(const AnimatedPositionedExampleApp()); class AnimatedPositionedExampleApp extends StatelessWidget { const AnimatedPositionedExampleApp({super.key}); + static const Duration duration = Duration(seconds: 2); + static const Curve curve = Curves.fastOutSlowIn; + @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('AnimatedPositioned Sample')), body: const Center( - child: AnimatedPositionedExample(), + child: AnimatedPositionedExample( + duration: duration, + curve: curve, + ), ), ), ); @@ -25,10 +31,19 @@ class AnimatedPositionedExampleApp extends StatelessWidget { } class AnimatedPositionedExample extends StatefulWidget { - const AnimatedPositionedExample({super.key}); + const AnimatedPositionedExample({ + required this.duration, + required this.curve, + super.key, + }); + + final Duration duration; + + final Curve curve; @override - State createState() => _AnimatedPositionedExampleState(); + State createState() => + _AnimatedPositionedExampleState(); } class _AnimatedPositionedExampleState extends State { @@ -45,8 +60,8 @@ class _AnimatedPositionedExampleState extends State { width: selected ? 200.0 : 50.0, height: selected ? 50.0 : 200.0, top: selected ? 50.0 : 150.0, - duration: const Duration(seconds: 2), - curve: Curves.fastOutSlowIn, + duration: widget.duration, + curve: widget.curve, child: GestureDetector( onTap: () { setState(() { diff --git a/examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart b/examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart index 35df561f6628..f6d75de9eb25 100644 --- a/examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart +++ b/examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart @@ -11,13 +11,19 @@ void main() => runApp(const SliverAnimatedOpacityExampleApp()); class SliverAnimatedOpacityExampleApp extends StatelessWidget { const SliverAnimatedOpacityExampleApp({super.key}); + static const Duration duration = Duration(milliseconds: 500); + static const Curve curve = Curves.easeInOut; + @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('SliverAnimatedOpacity Sample')), body: const Center( - child: SliverAnimatedOpacityExample(), + child: SliverAnimatedOpacityExample( + duration: duration, + curve: curve, + ), ), ), ); @@ -25,13 +31,23 @@ class SliverAnimatedOpacityExampleApp extends StatelessWidget { } class SliverAnimatedOpacityExample extends StatefulWidget { - const SliverAnimatedOpacityExample({super.key}); + const SliverAnimatedOpacityExample({ + required this.duration, + required this.curve, + super.key, + }); + + final Duration duration; + + final Curve curve; @override - State createState() => _SliverAnimatedOpacityExampleState(); + State createState() => + _SliverAnimatedOpacityExampleState(); } -class _SliverAnimatedOpacityExampleState extends State +class _SliverAnimatedOpacityExampleState + extends State with SingleTickerProviderStateMixin { bool _visible = true; @@ -40,7 +56,8 @@ class _SliverAnimatedOpacityExampleState extends State[ SliverAnimatedOpacity( opacity: _visible ? 1.0 : 0.0, - duration: const Duration(milliseconds: 500), + duration: widget.duration, + curve: widget.curve, sliver: SliverFixedExtentList( itemExtent: 100.0, delegate: SliverChildBuilderDelegate( diff --git a/examples/api/lib/widgets/pop_scope/pop_scope.0.dart b/examples/api/lib/widgets/pop_scope/pop_scope.0.dart index 2400b0905e01..e2ed446259e6 100644 --- a/examples/api/lib/widgets/pop_scope/pop_scope.0.dart +++ b/examples/api/lib/widgets/pop_scope/pop_scope.0.dart @@ -109,9 +109,9 @@ class _PageTwoState extends State<_PageTwo> { mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Page Two'), - PopScope( + PopScope( canPop: false, - onPopInvoked: (bool didPop) async { + onPopInvokedWithResult: (bool didPop, Object? result) async { if (didPop) { return; } diff --git a/examples/api/lib/widgets/pop_scope/pop_scope.1.dart b/examples/api/lib/widgets/pop_scope/pop_scope.1.dart new file mode 100644 index 000000000000..7a058837a548 --- /dev/null +++ b/examples/api/lib/widgets/pop_scope/pop_scope.1.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. + +// This sample demonstrates how to use a PopScope to wrap a widget that +// may pop the page with a result. + +import 'package:flutter/material.dart'; + +void main() => runApp(const NavigatorPopHandlerApp()); + +class NavigatorPopHandlerApp extends StatelessWidget { + const NavigatorPopHandlerApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + initialRoute: '/home', + onGenerateRoute: (RouteSettings settings) { + return switch (settings.name) { + '/two' => MaterialPageRoute( + builder: (BuildContext context) => const _PageTwo(), + ), + _ => MaterialPageRoute( + builder: (BuildContext context) => const _HomePage(), + ), + }; + }, + ); + } +} + +class _HomePage extends StatefulWidget { + const _HomePage(); + + @override + State<_HomePage> createState() => _HomePageState(); +} + +class _HomePageState extends State<_HomePage> { + FormData? _formData; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Page One'), + if (_formData != null) + Text('Hello ${_formData!.name}, whose favorite food is ${_formData!.favoriteFood}.'), + TextButton( + onPressed: () async { + final FormData formData = + await Navigator.of(context).pushNamed('/two') + ?? const FormData(); + if (formData != _formData) { + setState(() { + _formData = formData; + }); + } + }, + child: const Text('Next page'), + ), + ], + ), + ), + ); + } +} + +class _PopScopeWrapper extends StatelessWidget { + const _PopScopeWrapper({required this.child}); + + final Widget child; + + Future _showBackDialog(BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Are you sure?'), + content: const Text( + 'Are you sure you want to leave this page?', + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Never mind'), + onPressed: () { + Navigator.pop(context, false); + }, + ), + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Leave'), + onPressed: () { + Navigator.pop(context, true); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + // The result argument contains the pop result that is defined in `_PageTwo`. + onPopInvokedWithResult: (bool didPop, FormData? result) async { + if (didPop) { + return; + } + final bool shouldPop = await _showBackDialog(context) ?? false; + if (context.mounted && shouldPop) { + Navigator.pop(context, result); + } + }, + child: child, + ); + } +} + +// This is a PopScope wrapper over _PageTwoBody +class _PageTwo extends StatelessWidget { + const _PageTwo(); + + @override + Widget build(BuildContext context) { + return const _PopScopeWrapper( + child: _PageTwoBody(), + ); + } + +} + +class _PageTwoBody extends StatefulWidget { + const _PageTwoBody(); + + @override + State<_PageTwoBody> createState() => _PageTwoBodyState(); +} + +class _PageTwoBodyState extends State<_PageTwoBody> { + FormData _formData = const FormData(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Page Two'), + Form( + child: Column( + children: [ + TextFormField( + decoration: const InputDecoration( + hintText: 'Enter your name.', + ), + onChanged: (String value) { + _formData = _formData.copyWith( + name: value, + ); + }, + ), + TextFormField( + decoration: const InputDecoration( + hintText: 'Enter your favorite food.', + ), + onChanged: (String value) { + _formData = _formData.copyWith( + favoriteFood: value, + ); + }, + ), + ], + ), + ), + TextButton( + onPressed: () async { + Navigator.maybePop(context, _formData); + }, + child: const Text('Go back'), + ), + ], + ), + ), + ); + } +} + +@immutable +class FormData { + const FormData({ + this.name = '', + this.favoriteFood = '', + }); + + final String name; + final String favoriteFood; + + FormData copyWith({String? name, String? favoriteFood}) { + return FormData( + name: name ?? this.name, + favoriteFood: favoriteFood ?? this.favoriteFood, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is FormData + && other.name == name + && other.favoriteFood == favoriteFood; + } + + @override + int get hashCode => Object.hash(name, favoriteFood); +} diff --git a/examples/api/macos/Runner/AppDelegate.swift b/examples/api/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/api/macos/Runner/AppDelegate.swift +++ b/examples/api/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/api/pubspec.yaml b/examples/api/pubspec.yaml index def88dc4d073..5af33e162f27 100644 --- a/examples/api/pubspec.yaml +++ b/examples/api/pubspec.yaml @@ -11,14 +11,14 @@ environment: flutter: ">=2.5.0-6.0.pre.30 <3.0.0" dependencies: - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 flutter: sdk: flutter characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -34,7 +34,7 @@ dev_dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -47,7 +47,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,10 +79,10 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,4 +93,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3b3c +# PUBSPEC CHECKSUM: 5045 diff --git a/examples/api/test/material/dropdown_menu/dropdown_menu.2_test.dart b/examples/api/test/material/dropdown_menu/dropdown_menu.2_test.dart index e652b3f9ca6e..2e62b4228c41 100644 --- a/examples/api/test/material/dropdown_menu/dropdown_menu.2_test.dart +++ b/examples/api/test/material/dropdown_menu/dropdown_menu.2_test.dart @@ -10,7 +10,7 @@ import 'package:flutter_api_samples/material/dropdown_menu/dropdown_menu.2.dart' import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('DropdownMenu cursor behavoir', (WidgetTester tester) async { + testWidgets('DropdownMenu cursor behavior', (WidgetTester tester) async { await tester.pumpWidget( const example.DropdownMenuApp(), ); diff --git a/examples/api/test/widgets/dismissible/dismissible.0_test.dart b/examples/api/test/widgets/dismissible/dismissible.0_test.dart new file mode 100644 index 000000000000..9dcb80967760 --- /dev/null +++ b/examples/api/test/widgets/dismissible/dismissible.0_test.dart @@ -0,0 +1,81 @@ +// 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_api_samples/widgets/dismissible/dismissible.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + Future dismissHorizontally({ + required WidgetTester tester, + required Finder finder, + required AxisDirection direction, + }) async { + final double width = (tester.renderObject(finder) as RenderBox).size.width; + final double dx = width * 0.8; + + final Offset offset = switch (direction) { + AxisDirection.left => Offset(-dx, 0.0), + AxisDirection.right => Offset(dx, 0.0), + _ => throw ArgumentError('$direction is not supported'), + }; + + await tester.drag(finder, offset); + } + + testWidgets( + 'ListTiles can be dismissed from right to left', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.DismissibleExampleApp(), + ); + + for (final int index in [0, 33, 66, 99]) { + final ValueKey key = ValueKey(index); + + await tester.scrollUntilVisible(find.byKey(key), 100); + + expect(find.byKey(key), findsOneWidget); + + await dismissHorizontally( + tester: tester, + finder: find.byKey(key), + direction: AxisDirection.left, + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(key), findsNothing); + } + }, + ); + + testWidgets( + 'ListTiles can be dismissed from left to right', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.DismissibleExampleApp(), + ); + + for (final int index in [0, 33, 66, 99]) { + final ValueKey key = ValueKey(index); + + await tester.scrollUntilVisible(find.byKey(key), 100); + + expect(find.byKey(key), findsOneWidget); + + await dismissHorizontally( + tester: tester, + finder: find.byKey(key), + direction: AxisDirection.right, + ); + + await tester.pumpAndSettle(); + + expect(find.byKey(key), findsNothing); + } + }, + ); +} diff --git a/examples/api/test/widgets/focus_manager/focus_node.0_test.dart b/examples/api/test/widgets/focus_manager/focus_node.0_test.dart new file mode 100644 index 000000000000..bd017976f727 --- /dev/null +++ b/examples/api/test/widgets/focus_manager/focus_node.0_test.dart @@ -0,0 +1,191 @@ +// 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'; +import 'package:flutter_api_samples/widgets/focus_manager/focus_node.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'FocusNode gets focused and unfocused on ColorfulButton tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.FocusNodeExampleApp(), + ); + + final Element button = tester.element( + find.byType(example.ColorfulButton), + ); + + expect( + tester.binding.focusManager.primaryFocus?.context, + isNot(equals(button)), + ); + + // Tapping on ColorfulButton to focus on FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + expect( + tester.binding.focusManager.primaryFocus?.context, + equals(button), + ); + + // Tapping on ColorfulButton to unfocus from FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + expect( + tester.binding.focusManager.primaryFocus?.context, + isNot(equals(button)), + ); + }, + ); + + testWidgets( + 'FocusNode updates the text label when focused or unfocused', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.FocusNodeExampleApp(), + ); + + expect(find.text('Press to focus'), findsOneWidget); + expect(find.text("I'm in color! Press R,G,B!"), findsNothing); + + // Tapping on ColorfulButton to focus on FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + expect(find.text('Press to focus'), findsNothing); + expect(find.text("I'm in color! Press R,G,B!"), findsOneWidget); + + // Tapping on ColorfulButton to unfocus from FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + expect(find.text('Press to focus'), findsOneWidget); + expect(find.text("I'm in color! Press R,G,B!"), findsNothing); + }, + ); + + testWidgets( + 'FocusNode updates color of the Container according to the key events when focused', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.FocusNodeExampleApp(), + ); + + // Tapping on ColorfulButton to focus on FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + final Finder containerFinder = find.descendant( + of: find.byType(example.ColorfulButton), + matching: find.byType(Container), + ); + + Container container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyR); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.red)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyG); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.green)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyB); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.blue)); + }, + ); + + testWidgets( + 'FocusNode does not listen to the key events when unfocused', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.FocusNodeExampleApp(), + ); + + final Finder containerFinder = find.descendant( + of: find.byType(example.ColorfulButton), + matching: find.byType(Container), + ); + + Container container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyR); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyG); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyB); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + }, + ); + + testWidgets( + 'FocusNode sets color to the white when unfocused and sets it back to the selected one when focused', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.FocusNodeExampleApp(), + ); + + final Finder containerFinder = find.descendant( + of: find.byType(example.ColorfulButton), + matching: find.byType(Container), + ); + + Container container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + // Tapping on ColorfulButton to focus on FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + await tester.sendKeyEvent(LogicalKeyboardKey.keyR); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.red)); + + // Tapping on ColorfulButton to unfocus from FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.white)); + + // Tapping on ColorfulButton to focus on FocusNode. + await tester.tap(find.byType(example.ColorfulButton)); + await tester.pump(); + + container = tester.widget(containerFinder); + expect(container.color, equals(Colors.red)); + }, + ); +} diff --git a/examples/api/test/widgets/gesture_detector/gesture_detector.0_test.dart b/examples/api/test/widgets/gesture_detector/gesture_detector.0_test.dart new file mode 100644 index 000000000000..50cbe2b3f978 --- /dev/null +++ b/examples/api/test/widgets/gesture_detector/gesture_detector.0_test.dart @@ -0,0 +1,40 @@ +// 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_api_samples/widgets/gesture_detector/gesture_detector.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'GestureDetector updates icon color and text on tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.GestureDetectorExampleApp(), + ); + + Icon icon = tester.widget(find.byIcon(Icons.lightbulb_outline)); + + expect(find.text('TURN LIGHT ON'), findsOneWidget); + expect(icon.color, Colors.black); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + icon = tester.widget(find.byIcon(Icons.lightbulb_outline)); + + expect(find.text('TURN LIGHT OFF'), findsOneWidget); + expect(icon.color, Colors.yellow.shade600); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + icon = tester.widget(find.byIcon(Icons.lightbulb_outline)); + + expect(find.text('TURN LIGHT ON'), findsOneWidget); + expect(icon.color, Colors.black); + }, + ); +} diff --git a/examples/api/test/widgets/gesture_detector/gesture_detector.1_test.dart b/examples/api/test/widgets/gesture_detector/gesture_detector.1_test.dart new file mode 100644 index 000000000000..bf3dc25f216f --- /dev/null +++ b/examples/api/test/widgets/gesture_detector/gesture_detector.1_test.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 'package:flutter_api_samples/widgets/gesture_detector/gesture_detector.1.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'GestureDetector updates Container color on tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.GestureDetectorExampleApp(), + ); + + Container container = tester.widget( + find.ancestor( + of: find.byType(GestureDetector), + matching: find.byType(Container), + ), + ); + + expect(container.color, Colors.white); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + container = tester.widget( + find.ancestor( + of: find.byType(GestureDetector), + matching: find.byType(Container), + ), + ); + + expect(container.color, Colors.yellow); + + await tester.tap(find.byType(GestureDetector)); + await tester.pump(); + + container = tester.widget( + find.ancestor( + of: find.byType(GestureDetector), + matching: find.byType(Container), + ), + ); + + expect(container.color, Colors.white); + }, + ); +} diff --git a/examples/api/test/widgets/implicit_animations/animated_align.0_test.dart b/examples/api/test/widgets/implicit_animations/animated_align.0_test.dart new file mode 100644 index 000000000000..80072b837234 --- /dev/null +++ b/examples/api/test/widgets/implicit_animations/animated_align.0_test.dart @@ -0,0 +1,79 @@ +// 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_api_samples/widgets/implicit_animations/animated_align.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('AnimatedAlign animates on tap', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AnimatedAlignExampleApp(), + ); + + final Finder alignFinder = find.descendant( + of: find.byType(AnimatedAlign), + matching: find.byType(Align), + ); + + const Alignment beginAlignment = Alignment.bottomLeft; + const Alignment endAlignment = Alignment.topRight; + + Align align = tester.widget(alignFinder); + expect(align.alignment, beginAlignment); + + // Tap on the AnimatedAlignExample to start the forward animation. + await tester.tap(find.byType(example.AnimatedAlignExample)); + await tester.pump(); + + align = tester.widget(alignFinder); + expect(align.alignment, beginAlignment); + + // Advance animation to the middle. + await tester.pump(example.AnimatedAlignExampleApp.duration ~/ 2); + + align = tester.widget(alignFinder); + expect( + align.alignment, + Alignment.lerp( + beginAlignment, + endAlignment, + example.AnimatedAlignExampleApp.curve.transform(0.5), + ), + ); + + // Advance animation to the end. + await tester.pump(example.AnimatedAlignExampleApp.duration ~/ 2); + + align = tester.widget(alignFinder); + expect(align.alignment, endAlignment); + + // Tap on the AnimatedAlignExample again to start the reverse animation. + await tester.tap(find.byType(example.AnimatedAlignExample)); + await tester.pump(); + + align = tester.widget(alignFinder); + expect(align.alignment, endAlignment); + + // Advance animation to the middle. + await tester.pump(example.AnimatedAlignExampleApp.duration ~/ 2); + + align = tester.widget(alignFinder); + expect( + align.alignment, + Alignment.lerp( + endAlignment, + beginAlignment, + example.AnimatedAlignExampleApp.curve.transform(0.5), + ), + ); + + // Advance animation to the end. + await tester.pump(example.AnimatedAlignExampleApp.duration ~/ 2); + + align = tester.widget(alignFinder); + expect(align.alignment, beginAlignment); + }); +} diff --git a/examples/api/test/widgets/implicit_animations/animated_padding.0_test.dart b/examples/api/test/widgets/implicit_animations/animated_padding.0_test.dart new file mode 100644 index 000000000000..fa9d5a1d632b --- /dev/null +++ b/examples/api/test/widgets/implicit_animations/animated_padding.0_test.dart @@ -0,0 +1,50 @@ +// 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_api_samples/widgets/implicit_animations/animated_padding.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'AnimatedPadding animates on ElevatedButton tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.AnimatedPaddingExampleApp(), + ); + + Padding padding = tester.widget( + find.descendant( + of: find.byType(AnimatedPadding), + matching: find.byType(Padding), + ), + ); + expect(padding.padding, equals(EdgeInsets.zero)); + + await tester.tap(find.byType(ElevatedButton)); + await tester.pump(); + + padding = tester.widget( + find.descendant( + of: find.byType(AnimatedPadding), + matching: find.byType(Padding), + ), + ); + expect(padding.padding, equals(EdgeInsets.zero)); + + // Advance animation to the end by the 2-second duration specified in + // the example app. + await tester.pump(const Duration(seconds: 2)); + + padding = tester.widget( + find.descendant( + of: find.byType(AnimatedPadding), + matching: find.byType(Padding), + ), + ); + expect(padding.padding, equals(const EdgeInsets.all(100.0))); + }, + ); +} diff --git a/examples/api/test/widgets/implicit_animations/animated_positioned.0_test.dart b/examples/api/test/widgets/implicit_animations/animated_positioned.0_test.dart new file mode 100644 index 000000000000..88654eb7e07b --- /dev/null +++ b/examples/api/test/widgets/implicit_animations/animated_positioned.0_test.dart @@ -0,0 +1,91 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/implicit_animations/animated_positioned.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'AnimatedPositioned animates on tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.AnimatedPositionedExampleApp(), + ); + + final Finder positionedFinder = find.descendant( + of: find.byType(AnimatedPositioned), + matching: find.byType(Positioned), + ); + + const double beginWidth = 50.0; + const double endWidth = 200.0; + const double beginHeight = 200.0; + const double endHeight = 50.0; + const double beginTop = 150.0; + const double endTop = 50.0; + + Positioned positioned = tester.widget(positionedFinder); + expect(positioned.width, beginWidth); + expect(positioned.height, beginHeight); + expect(positioned.top, beginTop); + + // Tap on the 'Tap me' text to start the forward animation. + await tester.tap(find.text('Tap me')); + await tester.pump(); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, beginWidth); + expect(positioned.height, beginHeight); + expect(positioned.top, beginTop); + + // Advance animation to the middle. + await tester.pump(example.AnimatedPositionedExampleApp.duration ~/ 2); + + final double t = + example.AnimatedPositionedExampleApp.curve.transform(0.5); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, lerpDouble(beginWidth, endWidth, t)); + expect(positioned.height, lerpDouble(beginHeight, endHeight, t)); + expect(positioned.top, lerpDouble(beginTop, endTop, t)); + + // Advance animation to the end. + await tester.pump(example.AnimatedPositionedExampleApp.duration ~/ 2); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, endWidth); + expect(positioned.height, endHeight); + expect(positioned.top, endTop); + + // Tap on the 'Tap me' text again to start the reverse animation. + await tester.tap(find.text('Tap me')); + await tester.pump(); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, endWidth); + expect(positioned.height, endHeight); + expect(positioned.top, endTop); + + // Advance animation to the middle. + await tester.pump(example.AnimatedPositionedExampleApp.duration ~/ 2); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, lerpDouble(endWidth, beginWidth, t)); + expect(positioned.height, lerpDouble(endHeight, beginHeight, t)); + expect(positioned.top, lerpDouble(endTop, beginTop, t)); + + // Advance animation to the end. + await tester.pump(example.AnimatedPositionedExampleApp.duration ~/ 2); + + positioned = tester.widget(positionedFinder); + expect(positioned.width, beginWidth); + expect(positioned.height, beginHeight); + expect(positioned.top, beginTop); + }, + ); +} diff --git a/examples/api/test/widgets/implicit_animations/sliver_animated_opacity.0_test.dart b/examples/api/test/widgets/implicit_animations/sliver_animated_opacity.0_test.dart new file mode 100644 index 000000000000..8ac97867afc8 --- /dev/null +++ b/examples/api/test/widgets/implicit_animations/sliver_animated_opacity.0_test.dart @@ -0,0 +1,84 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/implicit_animations/sliver_animated_opacity.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'SilverAnimatedOpacity animates on FloatingActionButton tap', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.SliverAnimatedOpacityExampleApp(), + ); + + final Finder fadeTransitionFinder = find.descendant( + of: find.byType(SliverAnimatedOpacity), + matching: find.byType(SliverFadeTransition), + ); + + const double beginOpacity = 1.0; + const double endOpacity = 0.0; + + SliverFadeTransition fadeTransition = tester.widget(fadeTransitionFinder); + expect(fadeTransition.opacity.value, beginOpacity); + + // Tap on the FloatingActionButton to start the forward animation. + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect(fadeTransition.opacity.value, beginOpacity); + + // Advance animation to the middle. + await tester.pump(example.SliverAnimatedOpacityExampleApp.duration ~/ 2); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect( + fadeTransition.opacity.value, + lerpDouble( + beginOpacity, + endOpacity, + example.SliverAnimatedOpacityExampleApp.curve.transform(0.5), + ), + ); + + // Advance animation to the end. + await tester.pump(example.SliverAnimatedOpacityExampleApp.duration ~/ 2); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect(fadeTransition.opacity.value, endOpacity); + + // Tap on the FloatingActionButton again to start the reverse animation. + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect(fadeTransition.opacity.value, endOpacity); + + // Advance animation to the middle. + await tester.pump(example.SliverAnimatedOpacityExampleApp.duration ~/ 2); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect( + fadeTransition.opacity.value, + lerpDouble( + endOpacity, + beginOpacity, + example.SliverAnimatedOpacityExampleApp.curve.transform(0.5), + ), + ); + + // Advance animation to the end. + await tester.pump(example.SliverAnimatedOpacityExampleApp.duration ~/ 2); + + fadeTransition = tester.widget(fadeTransitionFinder); + expect(fadeTransition.opacity.value, beginOpacity); + }, + ); +} diff --git a/examples/api/test/widgets/overflow_bar/overflow_bar.0_test.dart b/examples/api/test/widgets/overflow_bar/overflow_bar.0_test.dart new file mode 100644 index 000000000000..56d67ec8c5a7 --- /dev/null +++ b/examples/api/test/widgets/overflow_bar/overflow_bar.0_test.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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/overflow_bar/overflow_bar.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('OverflowBar displays buttons', (WidgetTester tester) async { + await tester.pumpWidget( + const example.OverflowBarExampleApp(), + ); + + // Creates a finder that matches widgets of the given + // `widgetType`, ensuring that the given widgets exist + // inside of an OverflowBar. + Finder buttonsFinder(Type widgetType) { + return find.descendant( + of: find.byType(OverflowBar), + matching: find.byType(widgetType), + ); + } + + expect(buttonsFinder(TextButton), findsNWidgets(2)); + expect(buttonsFinder(OutlinedButton), findsOne); + }); +} diff --git a/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart b/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart new file mode 100644 index 000000000000..14266af52147 --- /dev/null +++ b/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart @@ -0,0 +1,67 @@ +// 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_api_samples/widgets/pop_scope/pop_scope.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +import '../navigator_utils.dart'; + +void main() { + testWidgets('Can choose to stay on page', (WidgetTester tester) async { + await tester.pumpWidget( + const example.NavigatorPopHandlerApp(), + ); + + expect(find.text('Page One'), findsOneWidget); + + await tester.tap(find.text('Next page')); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsNothing); + expect(find.text('Page Two'), findsOneWidget); + + await simulateSystemBack(); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsNothing); + expect(find.text('Page Two'), findsOneWidget); + expect(find.text('Are you sure?'), findsOneWidget); + + await tester.tap(find.text('Never mind')); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsNothing); + expect(find.text('Page Two'), findsOneWidget); + }); + + testWidgets('Can choose to go back with pop result', (WidgetTester tester) async { + await tester.pumpWidget( + const example.NavigatorPopHandlerApp(), + ); + + expect(find.text('Page One'), findsOneWidget); + expect(find.text('Page Two'), findsNothing); + + await tester.tap(find.text('Next page')); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsNothing); + expect(find.text('Page Two'), findsOneWidget); + + await tester.enterText(find.byType(TextFormField).first, 'John'); + await tester.pumpAndSettle(); + await tester.enterText(find.byType(TextFormField).last, 'Apple'); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Go back')); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsNothing); + expect(find.text('Page Two'), findsOneWidget); + expect(find.text('Are you sure?'), findsOneWidget); + + await tester.tap(find.text('Leave')); + await tester.pumpAndSettle(); + expect(find.text('Page One'), findsOneWidget); + expect(find.text('Page Two'), findsNothing); + expect(find.text('Are you sure?'), findsNothing); + expect(find.text('Hello John, whose favorite food is Apple.'), findsOneWidget); + }); +} diff --git a/examples/api/test/widgets/preferred_size/preferred_size.0_test.dart b/examples/api/test/widgets/preferred_size/preferred_size.0_test.dart new file mode 100644 index 000000000000..c6b1bb64df7d --- /dev/null +++ b/examples/api/test/widgets/preferred_size/preferred_size.0_test.dart @@ -0,0 +1,35 @@ +// 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_api_samples/widgets/preferred_size/preferred_size.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets( + 'PreferredSize determines the height of AppBarContent', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.PreferredSizeExampleApp(), + ); + + final PreferredSize preferredSize = tester.widget( + find.ancestor( + of: find.byType(example.AppBarContent), + matching: find.byType(PreferredSize), + ), + ); + + final RenderBox appBarContent = tester.renderObject( + find.byType(example.AppBarContent), + ) as RenderBox; + + expect( + preferredSize.preferredSize.height, + equals(appBarContent.size.height), + ); + }, + ); +} diff --git a/examples/api/test/widgets/shortcuts/character_activator.0_test.dart b/examples/api/test/widgets/shortcuts/character_activator.0_test.dart new file mode 100644 index 000000000000..fda1dccc21a9 --- /dev/null +++ b/examples/api/test/widgets/shortcuts/character_activator.0_test.dart @@ -0,0 +1,53 @@ +// 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'; +import 'package:flutter_api_samples/widgets/shortcuts/character_activator.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('CharacterActivatorExampleApp', () { + testWidgets('displays correct labels', (WidgetTester tester) async { + await tester.pumpWidget( + const example.CharacterActivatorExampleApp(), + ); + + expect(find.text('CharacterActivator Sample'), findsOneWidget); + expect(find.text('Press question mark for help'), findsOneWidget); + }); + + testWidgets( + 'shows snack bar on question key pressed', + (WidgetTester tester) async { + await tester.pumpWidget( + const example.CharacterActivatorExampleApp(), + ); + + final Finder snackBarFinder = find.ancestor( + of: find.text('Keep calm and carry on!'), + matching: find.byType(SnackBar), + ); + + expect(snackBarFinder, findsNothing); + + await tester.sendKeyEvent(LogicalKeyboardKey.slash, character: '?'); + + // Advance the SnackBar entrance animation to the end. + await tester.pumpAndSettle(); + + expect(snackBarFinder, findsOneWidget); + + // Advance time by default SnackBar display duration. + await tester.pump(const Duration(milliseconds: 4000)); + + // Advance the SnackBar exit animation to the end. + await tester.pumpAndSettle(); + + expect(snackBarFinder, findsNothing); + }, + ); + }); +} diff --git a/examples/api/test/widgets/table/table.0_test.dart b/examples/api/test/widgets/table/table.0_test.dart new file mode 100644 index 000000000000..869fd605e684 --- /dev/null +++ b/examples/api/test/widgets/table/table.0_test.dart @@ -0,0 +1,39 @@ +// 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_api_samples/widgets/table/table.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Table has expected arrangement', (WidgetTester tester) async { + await tester.pumpWidget(const example.TableExampleApp()); + + final Table table = tester.widget(find.byType(Table)); + + // Check the defined columnWidths. + expect(table.columnWidths, const { + 0: IntrinsicColumnWidth(), + 1: FlexColumnWidth(), + 2: FixedColumnWidth(64), + }); + + // The table has two rows. + expect(table.children.length, 2); + + for (int i = 0; i < table.children.length; i++) { + // Each row has three containers. + expect(table.children[i].children.length, 3); + + // Returns the width of given widget. + double getWidgetWidth(Widget widget) { + return tester.getSize(find.byWidget(widget)).width; + } + + // Check table row container width. + expect(getWidgetWidth(table.children[i].children.first), equals(128)); + expect(getWidgetWidth(table.children[i].children[2]), equals(64)); + } + }); +} diff --git a/examples/api/test/widgets/transitions/listenable_builder.1_test.dart b/examples/api/test/widgets/transitions/listenable_builder.1_test.dart index 00f18d1c28b1..9b33fb6f021f 100644 --- a/examples/api/test/widgets/transitions/listenable_builder.1_test.dart +++ b/examples/api/test/widgets/transitions/listenable_builder.1_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('Tapping FAB increments counter', (WidgetTester tester) async { await tester.pumpWidget(const example.ListenableBuilderExample()); - String getCount() => (tester.widget(find.descendant(of: find.byType(ListenableBuilder), matching: find.byType(Text))) as Text).data!; + String getCount() => (tester.widget(find.descendant(of: find.byType(ListenableBuilder).last, matching: find.byType(Text))) as Text).data!; expect(find.text('Current counter value:'), findsOneWidget); expect(find.text('0'), findsOneWidget); diff --git a/examples/flutter_view/macos/Runner/AppDelegate.swift b/examples/flutter_view/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/flutter_view/macos/Runner/AppDelegate.swift +++ b/examples/flutter_view/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/flutter_view/pubspec.yaml b/examples/flutter_view/pubspec.yaml index 2bfa177d2a9e..6e4cab61adf8 100644 --- a/examples/flutter_view/pubspec.yaml +++ b/examples/flutter_view/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" flutter: @@ -19,4 +19,4 @@ flutter: assets: - assets/flutter-mark-square-64.png -# PUBSPEC CHECKSUM: 9bc0 +# PUBSPEC CHECKSUM: edc2 diff --git a/examples/hello_world/macos/Runner/AppDelegate.swift b/examples/hello_world/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/hello_world/macos/Runner/AppDelegate.swift +++ b/examples/hello_world/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/hello_world/pubspec.yaml b/examples/hello_world/pubspec.yaml index 5554f3f2e0ba..2d10aeb91d73 100644 --- a/examples/hello_world/pubspec.yaml +++ b/examples/hello_world/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -18,7 +18,7 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,10 +60,10 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,4 +71,4 @@ dev_dependencies: webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/examples/image_list/macos/Runner/AppDelegate.swift b/examples/image_list/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/image_list/macos/Runner/AppDelegate.swift +++ b/examples/image_list/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/image_list/pubspec.yaml b/examples/image_list/pubspec.yaml index 9d36478dd412..408118b850c3 100644 --- a/examples/image_list/pubspec.yaml +++ b/examples/image_list/pubspec.yaml @@ -12,12 +12,12 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -43,8 +43,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -57,4 +57,4 @@ flutter: assets: - images/coast.jpg -# PUBSPEC CHECKSUM: e006 +# PUBSPEC CHECKSUM: ba0c diff --git a/examples/layers/macos/Runner/AppDelegate.swift b/examples/layers/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/layers/macos/Runner/AppDelegate.swift +++ b/examples/layers/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/layers/pubspec.yaml b/examples/layers/pubspec.yaml index f698f8cebbab..736fb4ae89f5 100644 --- a/examples/layers/pubspec.yaml +++ b/examples/layers/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -31,12 +31,12 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: assets: - services/data.json uses-material-design: true -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/examples/platform_channel/macos/Runner/AppDelegate.swift b/examples/platform_channel/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/platform_channel/macos/Runner/AppDelegate.swift +++ b/examples/platform_channel/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index 76427369378b..dbb2dd740868 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -18,7 +18,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,10 +60,10 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/examples/platform_channel_swift/ios/Runner/AppDelegate.swift b/examples/platform_channel_swift/ios/Runner/AppDelegate.swift index f5eadb693027..83b04716f915 100644 --- a/examples/platform_channel_swift/ios/Runner/AppDelegate.swift +++ b/examples/platform_channel_swift/ios/Runner/AppDelegate.swift @@ -19,7 +19,7 @@ enum MyFlutterErrorCode { static let unavailable = "UNAVAILABLE" } -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate, FlutterStreamHandler { private var eventSink: FlutterEventSink? diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 3f99fcae84bc..27a8184cee39 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -18,7 +18,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,7 +31,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,10 +60,10 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: db51 +# PUBSPEC CHECKSUM: 2f58 diff --git a/examples/platform_view/macos/Runner/AppDelegate.swift b/examples/platform_view/macos/Runner/AppDelegate.swift index d080d41951d3..73cc5fd1077c 100644 --- a/examples/platform_view/macos/Runner/AppDelegate.swift +++ b/examples/platform_view/macos/Runner/AppDelegate.swift @@ -5,7 +5,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/examples/platform_view/pubspec.yaml b/examples/platform_view/pubspec.yaml index 1e5b9a93fd3e..02699b703642 100644 --- a/examples/platform_view/pubspec.yaml +++ b/examples/platform_view/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" flutter: @@ -19,4 +19,4 @@ flutter: assets: - assets/flutter-mark-square-64.png -# PUBSPEC CHECKSUM: 9bc0 +# PUBSPEC CHECKSUM: edc2 diff --git a/examples/splash/pubspec.yaml b/examples/splash/pubspec.yaml index 09d9050337ab..491edbf52d8f 100644 --- a/examples/splash/pubspec.yaml +++ b/examples/splash/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -31,7 +31,7 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/examples/texture/pubspec.yaml b/examples/texture/pubspec.yaml index 531bfb2b01d0..42b149d8168f 100644 --- a/examples/texture/pubspec.yaml +++ b/examples/texture/pubspec.yaml @@ -10,13 +10,13 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: flutter_test: sdk: flutter - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -29,7 +29,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,14 +57,14 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # 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.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 870b +# PUBSPEC CHECKSUM: d912 diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index 25f4659eae04..ba80e1bec59d 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -1230,7 +1230,7 @@ class _DecelerateCurve extends Curve { @override double transformInternal(double t) { // Intended to match the behavior of: - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/DecelerateInterpolator.java + // https://android.googlesource.com/platform/frameworks/base/+/main/core/java/android/view/animation/DecelerateInterpolator.java // ...as of December 2016. t = 1.0 - t; return 1.0 - t * t; diff --git a/packages/flutter/lib/src/cupertino/dialog.dart b/packages/flutter/lib/src/cupertino/dialog.dart index bcbf5cd7e991..8424d461fa6e 100644 --- a/packages/flutter/lib/src/cupertino/dialog.dart +++ b/packages/flutter/lib/src/cupertino/dialog.dart @@ -639,14 +639,10 @@ class _CupertinoActionSheetState extends State { ), if (widget.cancelButton != null) _buildCancelButton(), ]; - - final Orientation orientation = MediaQuery.orientationOf(context); - final double actionSheetWidth; - if (orientation == Orientation.portrait) { - actionSheetWidth = MediaQuery.sizeOf(context).width - (_kActionSheetEdgeHorizontalPadding * 2); - } else { - actionSheetWidth = MediaQuery.sizeOf(context).height - (_kActionSheetEdgeHorizontalPadding * 2); - } + final double actionSheetWidth = switch (MediaQuery.orientationOf(context)) { + Orientation.portrait => MediaQuery.sizeOf(context).width, + Orientation.landscape => MediaQuery.sizeOf(context).height, + }; return SafeArea( child: ScrollConfiguration( @@ -660,7 +656,7 @@ class _CupertinoActionSheetState extends State { child: CupertinoUserInterfaceLevel( data: CupertinoUserInterfaceLevelData.elevated, child: Container( - width: actionSheetWidth, + width: actionSheetWidth - _kActionSheetEdgeHorizontalPadding * 2, margin: const EdgeInsets.symmetric( horizontal: _kActionSheetEdgeHorizontalPadding, vertical: _kActionSheetEdgeVerticalPadding, @@ -1485,22 +1481,15 @@ class _CupertinoAlertActionSection extends StatelessWidget { @override Widget build(BuildContext context) { - - final List interactiveButtons = []; - for (int i = 0; i < children.length; i += 1) { - interactiveButtons.add( - _PressableActionButton( - child: children[i], - ), - ); - } - return CupertinoScrollbar( controller: scrollController, child: SingleChildScrollView( controller: scrollController, child: _CupertinoDialogActionsRenderWidget( - actionButtons: interactiveButtons, + actionButtons: [ + for (final Widget child in children) + _PressableActionButton(child: child), + ], dividerThickness: _kDividerThickness, hasCancelButton: hasCancelButton, isActionSheet: isActionSheet, diff --git a/packages/flutter/lib/src/cupertino/magnifier.dart b/packages/flutter/lib/src/cupertino/magnifier.dart index f02803b5084b..fb20960aaa4c 100644 --- a/packages/flutter/lib/src/cupertino/magnifier.dart +++ b/packages/flutter/lib/src/cupertino/magnifier.dart @@ -82,8 +82,10 @@ class _CupertinoTextMagnifierState extends State // set these values. Offset _currentAdjustedMagnifierPosition = Offset.zero; double _verticalFocalPointAdjustment = 0; - late AnimationController _ioAnimationController; - late Animation _ioAnimation; + late final AnimationController _ioAnimationController; + late final Animation _ioAnimation; + late final CurvedAnimation _ioCurvedAnimation; + @override void initState() { @@ -97,20 +99,21 @@ class _CupertinoTextMagnifierState extends State widget.controller.animationController = _ioAnimationController; widget.magnifierInfo .addListener(_determineMagnifierPositionAndFocalPoint); - + _ioCurvedAnimation = CurvedAnimation( + parent: _ioAnimationController, + curve: widget.animationCurve, + ); _ioAnimation = Tween( begin: 0.0, end: 1.0, - ).animate(CurvedAnimation( - parent: _ioAnimationController, - curve: widget.animationCurve, - )); + ).animate(_ioCurvedAnimation); } @override void dispose() { widget.controller.animationController = null; _ioAnimationController.dispose(); + _ioCurvedAnimation.dispose(); widget.magnifierInfo .removeListener(_determineMagnifierPositionAndFocalPoint); super.dispose(); diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index b9a0659bf9e0..df7e8626cbaf 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -378,56 +378,109 @@ class CupertinoPage extends Page { /// /// The page slides in from the right and exits in reverse. It also shifts to the left in /// a parallax motion when another page enters to cover it. -class CupertinoPageTransition extends StatelessWidget { +class CupertinoPageTransition extends StatefulWidget { /// Creates an iOS-style page transition. /// + const CupertinoPageTransition({ + super.key, + required this.primaryRouteAnimation, + required this.secondaryRouteAnimation, + required this.child, + required this.linearTransition, + }); + + /// The widget below this widget in the tree. + final Widget child; + /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// when this screen is being pushed. + final Animation primaryRouteAnimation; + /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// when another screen is being pushed on top of this one. + final Animation secondaryRouteAnimation; + /// * `linearTransition` is whether to perform the transitions linearly. /// Used to precisely track back gesture drags. - CupertinoPageTransition({ - super.key, - required Animation primaryRouteAnimation, - required Animation secondaryRouteAnimation, - required this.child, - required bool linearTransition, - }) : _primaryPositionAnimation = - (linearTransition - ? primaryRouteAnimation - : CurvedAnimation( - parent: primaryRouteAnimation, + final bool linearTransition; + + @override + State createState() => _CupertinoPageTransitionState(); +} + +class _CupertinoPageTransitionState extends State { + + + // When this page is coming in to cover another page. + late Animation _primaryPositionAnimation; + // When this page is becoming covered by another page. + late Animation _secondaryPositionAnimation; + // Shadow of page which is coming in to cover another page. + late Animation _primaryShadowAnimation; + // Curve of primary page which is coming in to cover another page. + CurvedAnimation? _primaryPositionCurve; + // Curve of secondary page which is becoming covered by another page. + CurvedAnimation? _secondaryPositionCurve; + // Curve of primary page's shadow. + CurvedAnimation? _primaryShadowCurve; + + @override + void initState() { + super.initState(); + _setupAnimation(); + } + + @override + void didUpdateWidget(covariant CupertinoPageTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation + || oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation + || oldWidget.child != widget.child || oldWidget.linearTransition != widget.linearTransition) { + _disposeCurve(); + _setupAnimation(); + } + } + + @override + void dispose() { + super.dispose(); + _disposeCurve(); + } + + void _disposeCurve() { + _primaryPositionCurve?.dispose(); + _secondaryPositionCurve?.dispose(); + _primaryShadowCurve?.dispose(); + } + + void _setupAnimation() { + _primaryPositionAnimation = + (widget.linearTransition + ? widget.primaryRouteAnimation + : _primaryPositionCurve = CurvedAnimation( + parent: widget.primaryRouteAnimation, curve: Curves.fastEaseInToSlowEaseOut, reverseCurve: Curves.fastEaseInToSlowEaseOut.flipped, ) - ).drive(_kRightMiddleTween), + ).drive(_kRightMiddleTween); _secondaryPositionAnimation = - (linearTransition - ? secondaryRouteAnimation - : CurvedAnimation( - parent: secondaryRouteAnimation, + (widget.linearTransition + ? widget.secondaryRouteAnimation + : _secondaryPositionCurve = CurvedAnimation( + parent: widget.secondaryRouteAnimation, curve: Curves.linearToEaseOut, reverseCurve: Curves.easeInToLinear, ) - ).drive(_kMiddleLeftTween), + ).drive(_kMiddleLeftTween); _primaryShadowAnimation = - (linearTransition - ? primaryRouteAnimation - : CurvedAnimation( - parent: primaryRouteAnimation, + (widget.linearTransition + ? widget.primaryRouteAnimation + : _secondaryPositionCurve = CurvedAnimation( + parent: widget.primaryRouteAnimation, curve: Curves.linearToEaseOut, ) ).drive(_CupertinoEdgeShadowDecoration.kTween); - - // When this page is coming in to cover another page. - final Animation _primaryPositionAnimation; - // When this page is becoming covered by another page. - final Animation _secondaryPositionAnimation; - final Animation _primaryShadowAnimation; - - /// The widget below this widget in the tree. - final Widget child; + } @override Widget build(BuildContext context) { @@ -442,7 +495,7 @@ class CupertinoPageTransition extends StatelessWidget { textDirection: textDirection, child: DecoratedBoxTransition( decoration: _primaryShadowAnimation, - child: child, + child: widget.child, ), ), ); @@ -453,44 +506,96 @@ class CupertinoPageTransition extends StatelessWidget { /// /// For example, used when creating a new calendar event by bringing in the next /// screen from the bottom. -class CupertinoFullscreenDialogTransition extends StatelessWidget { +class CupertinoFullscreenDialogTransition extends StatefulWidget { /// Creates an iOS-style transition used for summoning fullscreen dialogs. /// + const CupertinoFullscreenDialogTransition({ + super.key, + required this.primaryRouteAnimation, + required this.secondaryRouteAnimation, + required this.child, + required this.linearTransition, + }); + /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// when this screen is being pushed. + final Animation primaryRouteAnimation; + /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 /// when another screen is being pushed on top of this one. - /// * `linearTransition` is whether to perform the secondary transition linearly. + final Animation secondaryRouteAnimation; + + /// * `linearTransition` is whether to perform the transitions linearly. /// Used to precisely track back gesture drags. - CupertinoFullscreenDialogTransition({ - super.key, - required Animation primaryRouteAnimation, - required Animation secondaryRouteAnimation, - required this.child, - required bool linearTransition, - }) : _positionAnimation = CurvedAnimation( - parent: primaryRouteAnimation, + final bool linearTransition; + + /// The widget below this widget in the tree. + final Widget child; + + @override + State createState() => _CupertinoFullscreenDialogTransitionState(); +} + +class _CupertinoFullscreenDialogTransitionState extends State { + /// When this page is coming in to cover another page. + late Animation _primaryPositionAnimation; + /// When this page is becoming covered by another page. + late Animation _secondaryPositionAnimation; + /// Curve of primary page which is coming in to cover another page. + CurvedAnimation? _primaryPositionCurve; + /// Curve of secondary page which is becoming covered by another page. + CurvedAnimation? _secondaryPositionCurve; + + @override + void initState() { + super.initState(); + _setupAnimation(); + } + + @override + void didUpdateWidget(covariant CupertinoFullscreenDialogTransition oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation || + oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation || + oldWidget.child != widget.child || + oldWidget.linearTransition != widget.linearTransition) { + _disposeCurve(); + _setupAnimation(); + } + } + + @override + void dispose() { + _disposeCurve(); + super.dispose(); + } + + void _disposeCurve() { + _primaryPositionCurve?.dispose(); + _secondaryPositionCurve?.dispose(); + _primaryPositionCurve = null; + _secondaryPositionCurve = null; + } + + void _setupAnimation() { + _primaryPositionAnimation = (_primaryPositionCurve = CurvedAnimation( + parent: widget.primaryRouteAnimation, curve: Curves.linearToEaseOut, // The curve must be flipped so that the reverse animation doesn't play // an ease-in curve, which iOS does not use. reverseCurve: Curves.linearToEaseOut.flipped, - ).drive(_kBottomUpTween), + )).drive(_kBottomUpTween); _secondaryPositionAnimation = - (linearTransition - ? secondaryRouteAnimation - : CurvedAnimation( - parent: secondaryRouteAnimation, + (widget.linearTransition + ? widget.secondaryRouteAnimation + : _secondaryPositionCurve = CurvedAnimation( + parent: widget.secondaryRouteAnimation, curve: Curves.linearToEaseOut, reverseCurve: Curves.easeInToLinear, ) ).drive(_kMiddleLeftTween); + } - final Animation _positionAnimation; - // When this page is becoming covered by another page. - final Animation _secondaryPositionAnimation; - - /// The widget below this widget in the tree. - final Widget child; @override Widget build(BuildContext context) { @@ -501,8 +606,8 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget { textDirection: textDirection, transformHitTests: false, child: SlideTransition( - position: _positionAnimation, - child: child, + position: _primaryPositionAnimation, + child: widget.child, ), ); } diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart index 76cc2ee61c19..ae9068dfb970 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart @@ -887,13 +887,12 @@ class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement { _mountChild(toolbarItems.nextButton, _CupertinoTextSelectionToolbarItemsSlot.nextButton); // Mount list children. - _children = List.filled(toolbarItems.children.length, _NullElement.instance); Element? previousChild; - for (int i = 0; i < _children.length; i += 1) { - final Element newChild = inflateWidget(toolbarItems.children[i], IndexedSlot(i, previousChild)); - _children[i] = newChild; - previousChild = newChild; - } + _children = List.generate(toolbarItems.children.length, (int i) { + final Element result = inflateWidget(toolbarItems.children[i], IndexedSlot(i, previousChild)); + previousChild = result; + return result; + }, growable: false); } @override @@ -1273,19 +1272,3 @@ enum _CupertinoTextSelectionToolbarItemsSlot { backButton, nextButton, } - -class _NullElement extends Element { - _NullElement() : super(const _NullWidget()); - - static _NullElement instance = _NullElement(); - - @override - bool get debugDoingBuild => throw UnimplementedError(); -} - -class _NullWidget extends Widget { - const _NullWidget(); - - @override - Element createElement() => throw UnimplementedError(); -} diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index fdb00b22f3ac..3dc43e38b190 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -2113,16 +2113,11 @@ class FlagProperty extends DiagnosticsProperty { @override String valueToString({ TextTreeConfiguration? parentConfiguration }) { - if (value ?? false) { - if (ifTrue != null) { - return ifTrue!; - } - } else if (value == false) { - if (ifFalse != null) { - return ifFalse!; - } - } - return super.valueToString(parentConfiguration: parentConfiguration); + return switch (value) { + true when ifTrue != null => ifTrue!, + false when ifFalse != null => ifFalse!, + _ => super.valueToString(parentConfiguration: parentConfiguration), + }; } @override @@ -2139,17 +2134,11 @@ class FlagProperty extends DiagnosticsProperty { @override DiagnosticLevel get level { - if (value ?? false) { - if (ifTrue == null) { - return DiagnosticLevel.hidden; - } - } - if (value == false) { - if (ifFalse == null) { - return DiagnosticLevel.hidden; - } - } - return super.level; + return switch (value) { + true when ifTrue == null => DiagnosticLevel.hidden, + false when ifFalse == null => DiagnosticLevel.hidden, + _ => super.level, + }; } } diff --git a/packages/flutter/lib/src/foundation/timeline.dart b/packages/flutter/lib/src/foundation/timeline.dart index 0f71c362a4e3..f5b2c72a091b 100644 --- a/packages/flutter/lib/src/foundation/timeline.dart +++ b/packages/flutter/lib/src/foundation/timeline.dart @@ -311,12 +311,10 @@ final class _Float64ListChain { /// are read back, they do not affect the timings of the work being /// benchmarked. List extractElements() { - final List result = []; - _chain.forEach(result.addAll); - for (int i = 0; i < _pointer; i++) { - result.add(_slice[i]); - } - return result; + return [ + for (final Float64List list in _chain) ...list, + for (int i = 0; i < _pointer; i++) _slice[i], + ]; } } @@ -349,16 +347,11 @@ final class _StringListChain { /// are read back, they do not affect the timings of the work being /// benchmarked. List extractElements() { - final List result = []; - for (final List slice in _chain) { - for (final String? element in slice) { - result.add(element!); - } - } - for (int i = 0; i < _pointer; i++) { - result.add(_slice[i]!); - } - return result; + return [ + for (final List slice in _chain) + for (final String? value in slice) value!, + for (int i = 0; i < _pointer; i++) _slice[i]!, + ]; } } diff --git a/packages/flutter/lib/src/gestures/constants.dart b/packages/flutter/lib/src/gestures/constants.dart index 6d83ad3769b0..269b6ca594b5 100644 --- a/packages/flutter/lib/src/gestures/constants.dart +++ b/packages/flutter/lib/src/gestures/constants.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. // Modeled after Android's ViewConfiguration: -// https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/ViewConfiguration.java +// https://github.com/android/platform_frameworks_base/blob/main/core/java/android/view/ViewConfiguration.java /// The time that must elapse before a tap gesture sends onTapDown, if there's /// any doubt that the gesture is a tap. diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 3ed4b3ad9915..d3f44152ff9e 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -669,7 +669,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { // it keeps track of the last accepted pointer. If this active pointer // leave up, it will be set to the first accepted pointer. // Refer to the implementation of Android `RecyclerView`(line 3846): - // https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java + // https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java int? _activePointer; @override diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index a61f3a0d7974..67c1a1ae1323 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -1230,9 +1230,9 @@ class _MasterDetailFlowState extends State<_MasterDetailFlow> implements _PageOp } MaterialPageRoute _detailPageRoute(Object? arguments) { - return MaterialPageRoute(builder: (BuildContext context) { - return PopScope( - onPopInvoked: (bool didPop) { + return MaterialPageRoute(builder: (BuildContext context) { + return PopScope( + onPopInvokedWithResult: (bool didPop, void result) { // No need for setState() as rebuild happens on navigation pop. focus = _Focus.master; }, diff --git a/packages/flutter/lib/src/material/badge.dart b/packages/flutter/lib/src/material/badge.dart index ebfbf1a6d939..fe3f6bd733de 100644 --- a/packages/flutter/lib/src/material/badge.dart +++ b/packages/flutter/lib/src/material/badge.dart @@ -2,6 +2,8 @@ // 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 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -161,27 +163,39 @@ class Badge extends StatelessWidget { final BadgeThemeData badgeTheme = BadgeTheme.of(context); final BadgeThemeData defaults = _BadgeDefaultsM3(context); - final double effectiveSmallSize = smallSize ?? badgeTheme.smallSize ?? defaults.smallSize!; - final double effectiveLargeSize = largeSize ?? badgeTheme.largeSize ?? defaults.largeSize!; - - final Widget badge = DefaultTextStyle( - style: (textStyle ?? badgeTheme.textStyle ?? defaults.textStyle!).copyWith( - color: textColor ?? badgeTheme.textColor ?? defaults.textColor!, - ), - child: IntrinsicWidth( - child: Container( - height: label == null ? effectiveSmallSize : effectiveLargeSize, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: backgroundColor ?? badgeTheme.backgroundColor ?? defaults.backgroundColor!, - shape: const StadiumBorder(), + final Decoration effectiveDecoration = ShapeDecoration( + color: backgroundColor ?? badgeTheme.backgroundColor ?? defaults.backgroundColor!, + shape: const StadiumBorder(), + ); + final double effectiveWidthOffset; + final Widget badge; + final bool hasLabel = label != null; + if (hasLabel) { + final double minSize = effectiveWidthOffset = largeSize ?? badgeTheme.largeSize ?? defaults.largeSize!; + badge = DefaultTextStyle( + style: (textStyle ?? badgeTheme.textStyle ?? defaults.textStyle!).copyWith( + color: textColor ?? badgeTheme.textColor ?? defaults.textColor!, + ), + child: _IntrinsicHorizontalStadium( + minSize: minSize, + child: Container( + clipBehavior: Clip.antiAlias, + decoration: effectiveDecoration, + padding: padding ?? badgeTheme.padding ?? defaults.padding!, + alignment: Alignment.center, + child: label, ), - padding: label == null ? null : (padding ?? badgeTheme.padding ?? defaults.padding!), - alignment: label == null ? null : Alignment.center, - child: label ?? SizedBox(width: effectiveSmallSize, height: effectiveSmallSize), ), - ), - ); + ); + } else { + final double effectiveSmallSize = effectiveWidthOffset = smallSize ?? badgeTheme.smallSize ?? defaults.smallSize!; + badge = Container( + width: effectiveSmallSize, + height: effectiveSmallSize, + clipBehavior: Clip.antiAlias, + decoration: effectiveDecoration, + ); + } if (child == null) { return badge; @@ -190,7 +204,10 @@ class Badge extends StatelessWidget { final AlignmentGeometry effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!; final TextDirection textDirection = Directionality.of(context); final Offset defaultOffset = textDirection == TextDirection.ltr ? const Offset(4, -4) : const Offset(-4, -4); - final Offset effectiveOffset = offset ?? badgeTheme.offset ?? defaultOffset; + // Adds a offset const Offset(0, 8) to avoiding breaking customers after + // the offset calculation changes. + // See https://github.com/flutter/flutter/pull/146853. + final Offset effectiveOffset = (offset ?? badgeTheme.offset ?? defaultOffset) + const Offset(0, 8); return Stack( @@ -200,7 +217,9 @@ class Badge extends StatelessWidget { Positioned.fill( child: _Badge( alignment: effectiveAlignment, - offset: label == null ? Offset.zero : effectiveOffset, + offset: hasLabel ? effectiveOffset : Offset.zero, + hasLabel: hasLabel, + widthOffset: effectiveWidthOffset, textDirection: textDirection, child: badge, ), @@ -214,18 +233,24 @@ class _Badge extends SingleChildRenderObjectWidget { const _Badge({ required this.alignment, required this.offset, + required this.widthOffset, required this.textDirection, + required this.hasLabel, super.child, // the badge }); final AlignmentGeometry alignment; final Offset offset; + final double widthOffset; final TextDirection textDirection; + final bool hasLabel; @override _RenderBadge createRenderObject(BuildContext context) { return _RenderBadge( alignment: alignment, + widthOffset: widthOffset, + hasLabel: hasLabel, offset: offset, textDirection: Directionality.maybeOf(context), ); @@ -236,6 +261,8 @@ class _Badge extends SingleChildRenderObjectWidget { renderObject ..alignment = alignment ..offset = offset + ..widthOffset = widthOffset + ..hasLabel = hasLabel ..textDirection = Directionality.maybeOf(context); } @@ -252,7 +279,11 @@ class _RenderBadge extends RenderAligningShiftedBox { super.textDirection, super.alignment, required Offset offset, - }) : _offset = offset; + required bool hasLabel, + required double widthOffset, + }) : _offset = offset, + _hasLabel = hasLabel, + _widthOffset = widthOffset; Offset get offset => _offset; Offset _offset; @@ -264,6 +295,26 @@ class _RenderBadge extends RenderAligningShiftedBox { markNeedsLayout(); } + bool get hasLabel => _hasLabel; + bool _hasLabel; + set hasLabel(bool value) { + if (_hasLabel == value) { + return; + } + _hasLabel = value; + markNeedsLayout(); + } + + double get widthOffset => _widthOffset; + double _widthOffset; + set widthOffset(double value) { + if (_widthOffset == value) { + return; + } + _widthOffset = value; + markNeedsLayout(); + } + @override void performLayout() { final BoxConstraints constraints = this.constraints; @@ -275,7 +326,104 @@ class _RenderBadge extends RenderAligningShiftedBox { final double badgeSize = child!.size.height; final Alignment resolvedAlignment = alignment.resolve(textDirection); final BoxParentData childParentData = child!.parentData! as BoxParentData; - childParentData.offset = offset + resolvedAlignment.alongOffset(Offset(size.width - badgeSize, size.height - badgeSize)); + Offset badgeLocation = offset + resolvedAlignment.alongOffset(Offset(size.width - widthOffset, size.height)); + if (hasLabel) { + // Adjust for label height. + badgeLocation = badgeLocation - Offset(0, badgeSize / 2); + } + childParentData.offset = badgeLocation; + } +} + +/// A widget size itself to the smallest horizontal stadium rect that can still +/// fit the child's intrinsic size. +/// +/// A horizontal stadium means a rect that has width >= height. +/// +/// Uses [minSize] to set the min size of width and height. +class _IntrinsicHorizontalStadium extends SingleChildRenderObjectWidget { + const _IntrinsicHorizontalStadium({super.child, required this.minSize}); + final double minSize; + + @override + _RenderIntrinsicHorizontalStadium createRenderObject(BuildContext context) { + return _RenderIntrinsicHorizontalStadium(minSize: minSize); + } +} + +class _RenderIntrinsicHorizontalStadium extends RenderProxyBox { + _RenderIntrinsicHorizontalStadium({ + RenderBox? child, + required double minSize, + }) : _minSize = minSize, + super(child); + + double get minSize => _minSize; + double _minSize; + set minSize(double value) { + if (_minSize == value) { + return; + } + _minSize = value; + markNeedsLayout(); + } + + @override + double computeMinIntrinsicWidth(double height) { + return getMaxIntrinsicWidth(height); + } + + @override + double computeMaxIntrinsicWidth(double height) { + return math.max(getMaxIntrinsicHeight(double.infinity), super.computeMaxIntrinsicWidth(height)); + } + + @override + double computeMinIntrinsicHeight(double width) { + return getMaxIntrinsicHeight(width); + } + + @override + double computeMaxIntrinsicHeight(double width) { + return math.max(minSize, super.computeMaxIntrinsicHeight(width)); + } + + BoxConstraints _childConstraints(RenderBox child, BoxConstraints constraints) { + final double childHeight = math.max(minSize, child.getMaxIntrinsicHeight(constraints.maxWidth)); + final double childWidth = child.getMaxIntrinsicWidth(constraints.maxHeight); + return constraints.tighten(width: math.max(childWidth, childHeight), height: childHeight); + } + + Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) { + final RenderBox child = this.child!; + final Size childSize = layoutChild(child, _childConstraints(child, constraints)); + if (childSize.height > childSize.width) { + return Size(childSize.height, childSize.height); + } + return childSize; + } + + @override + @protected + Size computeDryLayout(covariant BoxConstraints constraints) { + return _computeSize( + layoutChild: ChildLayoutHelper.dryLayoutChild, + constraints: constraints, + ); + } + + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + final RenderBox child = this.child!; + return child.getDryBaseline(_childConstraints(child, constraints), baseline); + } + + @override + void performLayout() { + size = _computeSize( + layoutChild: ChildLayoutHelper.layoutChild, + constraints: constraints, + ); } } diff --git a/packages/flutter/lib/src/material/banner.dart b/packages/flutter/lib/src/material/banner.dart index 67d15b8b7590..6a92208d1d1b 100644 --- a/packages/flutter/lib/src/material/banner.dart +++ b/packages/flutter/lib/src/material/banner.dart @@ -267,11 +267,15 @@ class MaterialBanner extends StatefulWidget { class _MaterialBannerState extends State { bool _wasVisible = false; + CurvedAnimation? _heightAnimation; + CurvedAnimation? _slideOutCurvedAnimation; @override void initState() { super.initState(); widget.animation?.addStatusListener(_onAnimationStatusChanged); + _setCurvedAnimations(); + } @override @@ -280,12 +284,31 @@ class _MaterialBannerState extends State { if (widget.animation != oldWidget.animation) { oldWidget.animation?.removeStatusListener(_onAnimationStatusChanged); widget.animation?.addStatusListener(_onAnimationStatusChanged); + _setCurvedAnimations(); } } + void _setCurvedAnimations() { + _heightAnimation?.dispose(); + _slideOutCurvedAnimation?.dispose(); + if (widget.animation != null) { + _heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _materialBannerHeightCurve); + _slideOutCurvedAnimation = CurvedAnimation( + parent: widget.animation!, + curve: const Threshold(0.0), + ); + } else { + _heightAnimation = null; + _slideOutCurvedAnimation = null; + } + + } + @override void dispose() { widget.animation?.removeStatusListener(_onAnimationStatusChanged); + _heightAnimation?.dispose(); + _slideOutCurvedAnimation?.dispose(); super.dispose(); } @@ -408,14 +431,10 @@ class _MaterialBannerState extends State { child: materialBanner, ); - final CurvedAnimation heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _materialBannerHeightCurve); final Animation slideOutAnimation = Tween( begin: const Offset(0.0, -1.0), end: Offset.zero, - ).animate(CurvedAnimation( - parent: widget.animation!, - curve: const Threshold(0.0), - )); + ).animate(_slideOutCurvedAnimation!); materialBanner = Semantics( container: true, @@ -436,11 +455,11 @@ class _MaterialBannerState extends State { materialBannerTransition = materialBanner; } else { materialBannerTransition = AnimatedBuilder( - animation: heightAnimation, + animation: _heightAnimation!, builder: (BuildContext context, Widget? child) { return Align( alignment: AlignmentDirectional.bottomStart, - heightFactor: heightAnimation.value, + heightFactor: _heightAnimation!.value, child: child, ); }, diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index 6e981ec86bf7..7ef3b4db0a9f 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -802,7 +802,7 @@ class _Label extends StatelessWidget { class _BottomNavigationBarState extends State with TickerProviderStateMixin { List _controllers = []; - late List _animations; + List _animations = []; // A queue of color splashes currently being animated. final Queue<_Circle> _circles = Queue<_Circle>(); @@ -820,6 +820,9 @@ class _BottomNavigationBarState extends State with TickerPr for (final _Circle circle in _circles) { circle.dispose(); } + for (final CurvedAnimation animation in _animations) { + animation.dispose(); + } _circles.clear(); _controllers = List.generate(widget.items.length, (int index) { @@ -883,6 +886,9 @@ class _BottomNavigationBarState extends State with TickerPr for (final _Circle circle in _circles) { circle.dispose(); } + for (final CurvedAnimation animation in _animations) { + animation.dispose(); + } super.dispose(); } @@ -1251,6 +1257,7 @@ class _Circle { void dispose() { controller.dispose(); + animation.dispose(); } } diff --git a/packages/flutter/lib/src/material/button_style.dart b/packages/flutter/lib/src/material/button_style.dart index d2c7dffca003..9d999b5ab99f 100644 --- a/packages/flutter/lib/src/material/button_style.dart +++ b/packages/flutter/lib/src/material/button_style.dart @@ -210,6 +210,25 @@ class ButtonStyle with Diagnosticable { final MaterialStateProperty? elevation; /// The padding between the button's boundary and its child. + /// + /// The vertical aspect of the default or user-specified padding is adjusted + /// automatically based on [visualDensity]. + /// + /// When the visual density is [VisualDensity.compact], the top and bottom insets + /// are reduced by 8 pixels or set to 0 pixels if the result of the reduced padding + /// is negative. For example: the visual density defaults to [VisualDensity.compact] + /// on desktop and web, so if the provided padding is 16 pixels on the top and bottom, + /// it will be reduced to 8 pixels on the top and bottom. If the provided padding + /// is 4 pixels, the result will be no padding on the top and bottom. + /// + /// When the visual density is [VisualDensity.comfortable], the top and bottom insets + /// are reduced by 4 pixels or set to 0 pixels if the result of the reduced padding + /// is negative. + /// + /// When the visual density is [VisualDensity.standard] the top and bottom insets + /// are not changed. The visual density defaults to [VisualDensity.standard] on mobile. + /// + /// See [ThemeData.visualDensity] for more details. final MaterialStateProperty? padding; /// The minimum size of the button itself. diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index aa25b7e1f86d..bb9ab5856456 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -211,18 +211,12 @@ class _CalendarDatePickerState extends State { _vibrate(); setState(() { _mode = mode; - if (_selectedDate != null) { - if (_mode == DatePickerMode.day) { - SemanticsService.announce( - _localizations.formatMonthYear(_selectedDate!), - _textDirection, - ); - } else { - SemanticsService.announce( - _localizations.formatYear(_selectedDate!), - _textDirection, - ); - } + if (_selectedDate case final DateTime selected) { + final String message = switch (mode) { + DatePickerMode.day => _localizations.formatMonthYear(selected), + DatePickerMode.year => _localizations.formatYear(selected), + }; + SemanticsService.announce(message, _textDirection); } }); } diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 7dd1253a7ebf..95a9338b4fdd 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -27,7 +27,6 @@ import 'tooltip.dart'; // Some design constants const double _kChipHeight = 32.0; -const double _kDeleteIconSize = 18.0; const int _kCheckmarkAlpha = 0xde; // 87% const int _kDisabledAlpha = 0x61; // 38% @@ -41,7 +40,7 @@ const Duration _kReverseDrawerDuration = Duration(milliseconds: 100); const Duration _kDisableDuration = Duration(milliseconds: 75); const Color _kSelectScrimColor = Color(0x60191919); -const Icon _kDefaultDeleteIcon = Icon(Icons.cancel, size: _kDeleteIconSize); +const Icon _kDefaultDeleteIcon = Icon(Icons.cancel); /// An interface defining the base attributes for a Material Design chip. /// @@ -262,7 +261,17 @@ abstract interface class ChipAttributes { abstract interface class DeletableChipAttributes { /// The icon displayed when [onDeleted] is set. /// - /// Defaults to an [Icon] widget set to use [Icons.cancel]. + /// If [deleteIconColor] is provided, it will be used as the color of the + /// delete icon. If [deleteIconColor] is null, then the icon will use the + /// color specified in the chip [IconTheme]. If the [IconTheme] is null, then + /// the icon will use the color specified in the [ThemeData.iconTheme]. + /// + /// If a size is specified in the chip [IconTheme], then the delete icon will + /// use that size. Otherwise, defaults to 18 pixels. + /// + /// Defaults to an [Icon] widget set to use [Icons.clear]. + /// If [ThemeData.useMaterial3] is false, then defaults to an [Icon] widget + /// set to use [Icons.cancel]. Widget? get deleteIcon; /// Called when the user taps the [deleteIcon] to delete the chip. @@ -1179,6 +1188,20 @@ class _RawChipState extends State with MaterialStateMixin, TickerProvid if (!hasDeleteButton) { return null; } + final IconThemeData iconTheme = widget.iconTheme + ?? chipTheme.iconTheme + ?? theme.chipTheme.iconTheme + ?? _ChipDefaultsM3(context, widget.isEnabled).iconTheme!; + final Color? effectiveDeleteIconColor = widget.deleteIconColor + ?? chipTheme.deleteIconColor + ?? theme.chipTheme.deleteIconColor + ?? widget.iconTheme?.color + ?? chipTheme.iconTheme?.color + ?? chipDefaults.deleteIconColor; + final double effectiveIconSize = widget.iconTheme?.size + ?? chipTheme.iconTheme?.size + ?? theme.chipTheme.iconTheme?.size + ?? _ChipDefaultsM3(context, widget.isEnabled).iconTheme!.size!; return Semantics( container: true, button: true, @@ -1194,11 +1217,9 @@ class _RawChipState extends State with MaterialStateMixin, TickerProvid customBorder: const CircleBorder(), onTap: widget.isEnabled ? widget.onDeleted : null, child: IconTheme( - data: theme.iconTheme.copyWith( - color: widget.deleteIconColor - ?? chipTheme.deleteIconColor - ?? theme.chipTheme.deleteIconColor - ?? chipDefaults.deleteIconColor, + data: iconTheme.copyWith( + color: effectiveDeleteIconColor, + size: effectiveIconSize, ), child: widget.deleteIcon, ), diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index c4ccb013f31c..57dd99c8956e 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -1271,11 +1271,11 @@ class _SortArrow extends StatefulWidget { } class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { - late AnimationController _opacityController; - late Animation _opacityAnimation; + late final AnimationController _opacityController; + late final CurvedAnimation _opacityAnimation; - late AnimationController _orientationController; - late Animation _orientationAnimation; + late final AnimationController _orientationController; + late final Animation _orientationAnimation; double _orientationOffset = 0.0; bool? _up; @@ -1354,6 +1354,7 @@ class _SortArrowState extends State<_SortArrow> with TickerProviderStateMixin { void dispose() { _opacityController.dispose(); _orientationController.dispose(); + _opacityAnimation.dispose(); super.dispose(); } diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index f4f832f6f6ab..890ae8d74750 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -1660,7 +1660,7 @@ class _CalendarRangePickerDialog extends StatelessWidget { actionsIconTheme: iconTheme, elevation: useMaterial3 ? 0 : null, scrolledUnderElevation: useMaterial3 ? 0 : null, - backgroundColor: useMaterial3 ? headerBackground : null, + backgroundColor: headerBackground, leading: CloseButton( onPressed: onCancel, ), diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index ec57dd77f329..1aa48bd8a980 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -118,6 +118,40 @@ class _DropdownMenuItemButton extends StatefulWidget { } class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> { + CurvedAnimation? _opacityAnimation; + + @override + void initState() { + super.initState(); + _setOpacityAnimation(); + } + + @override + void didUpdateWidget(_DropdownMenuItemButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.itemIndex != widget.itemIndex || + oldWidget.route.animation != widget.route.animation || + oldWidget.route.selectedIndex != widget.route.selectedIndex || + widget.route.items.length != oldWidget.route.items.length) { + _setOpacityAnimation(); + } + } + + void _setOpacityAnimation() { + _opacityAnimation?.dispose(); + final double unit = 0.5 / (widget.route.items.length + 1.5); + if (widget.itemIndex == widget.route.selectedIndex) { + _opacityAnimation = CurvedAnimation( + parent: widget.route.animation!, curve: const Threshold(0.0)); + } else { + final double start = + clampDouble(0.5 + (widget.itemIndex + 1) * unit, 0.0, 1.0); + final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); + _opacityAnimation = CurvedAnimation( + parent: widget.route.animation!, curve: Interval(start, end)); + } + } + void _handleFocusChange(bool focused) { final bool inTraditionalMode = switch (FocusManager.instance.highlightMode) { FocusHighlightMode.touch => false, @@ -156,18 +190,16 @@ class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up), }; + @override + void dispose() { + _opacityAnimation?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - final DropdownMenuItem dropdownMenuItem = widget.route.items[widget.itemIndex].item!; - final CurvedAnimation opacity; - final double unit = 0.5 / (widget.route.items.length + 1.5); - if (widget.itemIndex == widget.route.selectedIndex) { - opacity = CurvedAnimation(parent: widget.route.animation!, curve: const Threshold(0.0)); - } else { - final double start = clampDouble(0.5 + (widget.itemIndex + 1) * unit, 0.0, 1.0); - final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); - opacity = CurvedAnimation(parent: widget.route.animation!, curve: Interval(start, end)); - } + final DropdownMenuItem dropdownMenuItem = + widget.route.items[widget.itemIndex].item!; Widget child = Container( padding: widget.padding, height: widget.route.itemHeight, @@ -183,7 +215,7 @@ class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> child: child, ); } - child = FadeTransition(opacity: opacity, child: child); + child = FadeTransition(opacity: _opacityAnimation!, child: child); if (kIsWeb && dropdownMenuItem.enabled) { child = Shortcuts( shortcuts: _webShortcuts, @@ -221,8 +253,8 @@ class _DropdownMenu extends StatefulWidget { } class _DropdownMenuState extends State<_DropdownMenu> { - late CurvedAnimation _fadeOpacity; - late CurvedAnimation _resize; + late final CurvedAnimation _fadeOpacity; + late final CurvedAnimation _resize; @override void initState() { @@ -243,6 +275,13 @@ class _DropdownMenuState extends State<_DropdownMenu> { ); } + @override + void dispose() { + _fadeOpacity.dispose(); + _resize.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { // The menu is shown in three stages (unit timing in brackets): diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index fab75cba9464..6cd45acce55e 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -543,6 +543,11 @@ class ExpansionTile extends StatefulWidget { /// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used. /// Otherwise, defaults to [Curves.easeIn]. /// + /// If [AnimationStyle.reverseCurve] is provided, it will be used to override + /// the collapse animation curve. If it is null, then [AnimationStyle.reverseCurve] + /// from the [ExpansionTileThemeData.expansionAnimationStyle] will be used. + /// Otherwise, the same curve will be used as for expansion. + /// /// To disable the theme animation, use [AnimationStyle.noAnimation]. /// /// {@tool dartpad} @@ -566,11 +571,11 @@ class _ExpansionTileState extends State with SingleTickerProvider final ColorTween _headerColorTween = ColorTween(); final ColorTween _iconColorTween = ColorTween(); final ColorTween _backgroundColorTween = ColorTween(); - final CurveTween _heightFactorTween = CurveTween(curve: Curves.easeIn); + final Tween _heightFactorTween = Tween(begin: 0.0, end: 1.0); late AnimationController _animationController; late Animation _iconTurns; - late Animation _heightFactor; + late CurvedAnimation _heightFactor; late Animation _border; late Animation _headerColor; late Animation _iconColor; @@ -584,7 +589,10 @@ class _ExpansionTileState extends State with SingleTickerProvider void initState() { super.initState(); _animationController = AnimationController(duration: _kExpand, vsync: this); - _heightFactor = _animationController.drive(_heightFactorTween); + _heightFactor = CurvedAnimation( + parent: _animationController.drive(_heightFactorTween), + curve: Curves.easeIn, + ); _iconTurns = _animationController.drive(_halfTween.chain(_easeInTween)); _border = _animationController.drive(_borderTween.chain(_easeOutTween)); _headerColor = _animationController.drive(_headerColorTween.chain(_easeInTween)); @@ -862,9 +870,11 @@ class _ExpansionTileState extends State with SingleTickerProvider } void _updateHeightFactorCurve(ExpansionTileThemeData expansionTileTheme) { - _heightFactorTween.curve = widget.expansionAnimationStyle?.curve + _heightFactor.curve = widget.expansionAnimationStyle?.curve ?? expansionTileTheme.expansionAnimationStyle?.curve ?? Curves.easeIn; + _heightFactor.reverseCurve = widget.expansionAnimationStyle?.reverseCurve + ?? expansionTileTheme.expansionAnimationStyle?.reverseCurve; } @override diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index 06aed53f5a6f..d4005ccf627d 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -266,19 +266,20 @@ class FloatingActionButton extends StatelessWidget { /// The default foreground color for icons and text within the button. /// - /// If this property is null, then the - /// [FloatingActionButtonThemeData.foregroundColor] of - /// [ThemeData.floatingActionButtonTheme] is used. If that property is also - /// null, then the [ColorScheme.onSecondary] color of [ThemeData.colorScheme] - /// is used. + /// If this property is null, then the [FloatingActionButtonThemeData.foregroundColor] + /// of [ThemeData.floatingActionButtonTheme] is used. If that property is also + /// null, then the [ColorScheme.onPrimaryContainer] color of [ThemeData.colorScheme] + /// is used. If [ThemeData.useMaterial3] is set to false, then the + /// [ColorScheme.onSecondary] color of [ThemeData.colorScheme] is used. final Color? foregroundColor; /// The button's background color. /// - /// If this property is null, then the - /// [FloatingActionButtonThemeData.backgroundColor] of - /// [ThemeData.floatingActionButtonTheme] is used. If that property is also - /// null, then the [Theme]'s [ColorScheme.secondary] color is used. + /// If this property is null, then the [FloatingActionButtonThemeData.backgroundColor] + /// of [ThemeData.floatingActionButtonTheme] is used. If that property is also + /// null, then the [ColorScheme.primaryContainer] color of [ThemeData.colorScheme] + /// is used. If [ThemeData.useMaterial3] is set to false, then the + /// [ColorScheme.secondary] color of [ThemeData.colorScheme] is used. final Color? backgroundColor; /// The color to use for filling the button when the button has input focus. diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index bee7558d0709..30895a62fc2a 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -266,7 +266,7 @@ class IconButton extends StatelessWidget { }) : assert(splashRadius == null || splashRadius > 0), _variant = _IconButtonVariant.filledTonal; - /// Create a filled tonal variant of IconButton. + /// Create an outlined variant of IconButton. /// /// Outlined icon buttons are medium-emphasis buttons. They’re useful when an /// icon button needs more emphasis than a standard icon button but less than @@ -573,9 +573,15 @@ class IconButton extends StatelessWidget { /// [ButtonStyle.foregroundColor] value. Specify a value for [foregroundColor] /// to specify the color of the button's icons. The [hoverColor], [focusColor] /// and [highlightColor] colors are used to indicate the hover, focus, - /// and pressed states. Use [backgroundColor] for the button's background - /// fill color. Use [disabledForegroundColor] and [disabledBackgroundColor] - /// to specify the button's disabled icon and fill color. + /// and pressed states if [overlayColor] isn't specified. + /// + /// If [overlayColor] is specified and its value is [Colors.transparent] + /// then the pressed/focused/hovered highlights are effectively defeated. + /// Otherwise a [MaterialStateProperty] with the same opacities as the + /// default is created. + /// + /// Use [backgroundColor] for the button's background fill color. Use [disabledForegroundColor] + /// and [disabledBackgroundColor] to specify the button's disabled icon and fill color. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] /// parameters are used to construct [ButtonStyle].mouseCursor. @@ -611,6 +617,7 @@ class IconButton extends StatelessWidget { Color? highlightColor, Color? shadowColor, Color? surfaceTintColor, + Color? overlayColor, double? elevation, Size? minimumSize, Size? fixedSize, @@ -629,20 +636,24 @@ class IconButton extends StatelessWidget { InteractiveInkFeatureFactory? splashFactory, }) { final MaterialStateProperty? buttonBackgroundColor = (backgroundColor == null && disabledBackgroundColor == null) - ? null - : _IconButtonDefaultBackground(backgroundColor, disabledBackgroundColor); + ? null + : _IconButtonDefaultBackground(backgroundColor, disabledBackgroundColor); final MaterialStateProperty? buttonForegroundColor = (foregroundColor == null && disabledForegroundColor == null) + ? null + : _IconButtonDefaultForeground(foregroundColor, disabledForegroundColor); + final MaterialStateProperty? overlayColorProp = (foregroundColor == null && + hoverColor == null && focusColor == null && highlightColor == null && overlayColor == null) ? null - : _IconButtonDefaultForeground(foregroundColor, disabledForegroundColor); - final MaterialStateProperty? overlayColor = (foregroundColor == null && hoverColor == null && focusColor == null && highlightColor == null) - ? null - : _IconButtonDefaultOverlay(foregroundColor, focusColor, hoverColor, highlightColor); + : switch (overlayColor) { + (final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), + _ => _IconButtonDefaultOverlay(foregroundColor, focusColor, hoverColor, highlightColor, overlayColor), + }; final MaterialStateProperty mouseCursor = _IconButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( backgroundColor: buttonBackgroundColor, foregroundColor: buttonForegroundColor, - overlayColor: overlayColor, + overlayColor: overlayColorProp, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), elevation: ButtonStyleButton.allOrNull(elevation), @@ -1023,34 +1034,41 @@ class _IconButtonDefaultForeground extends MaterialStateProperty { @immutable class _IconButtonDefaultOverlay extends MaterialStateProperty { - _IconButtonDefaultOverlay(this.foregroundColor, this.focusColor, this.hoverColor, this.highlightColor); + _IconButtonDefaultOverlay( + this.foregroundColor, + this.focusColor, + this.hoverColor, + this.highlightColor, + this.overlayColor, + ); final Color? foregroundColor; final Color? focusColor; final Color? hoverColor; final Color? highlightColor; + final Color? overlayColor; @override Color? resolve(Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return highlightColor ?? foregroundColor?.withOpacity(0.1); + return highlightColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return hoverColor ?? foregroundColor?.withOpacity(0.08); + return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return focusColor ?? foregroundColor?.withOpacity(0.1); + return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } } if (states.contains(MaterialState.pressed)) { - return highlightColor ?? foregroundColor?.withOpacity(0.1); + return highlightColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return hoverColor ?? foregroundColor?.withOpacity(0.08); + return hoverColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return focusColor ?? foregroundColor?.withOpacity(0.1); + return focusColor ?? (overlayColor ?? foregroundColor)?.withOpacity(0.1); } return null; } diff --git a/packages/flutter/lib/src/material/ink_sparkle.dart b/packages/flutter/lib/src/material/ink_sparkle.dart index 798604985aec..4224e41d03ea 100644 --- a/packages/flutter/lib/src/material/ink_sparkle.dart +++ b/packages/flutter/lib/src/material/ink_sparkle.dart @@ -316,9 +316,9 @@ class InkSparkle extends InteractiveInkFeature { /// All double values for uniforms come from the Android 12 ripple /// implementation from the following files: - /// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java - /// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java - /// - https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/drawable/RippleAnimationSession.java + /// - https://cs.android.com/android/platform/superproject/+/main:frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java + /// - https://cs.android.com/android/platform/superproject/+/main:frameworks/base/graphics/java/android/graphics/drawable/RippleDrawable.java + /// - https://cs.android.com/android/platform/superproject/+/main:frameworks/base/graphics/java/android/graphics/drawable/RippleAnimationSession.java void _updateFragmentShader() { const double turbulenceScale = 1.5; final double turbulencePhase = _turbulenceSeed + _radiusScale.value; diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index b9a449f35c02..f50f5798337a 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -562,13 +562,11 @@ class FloatingLabelAlignment { } static String _stringify(double x) { - if (x == -1.0) { - return 'FloatingLabelAlignment.start'; - } - if (x == 0.0) { - return 'FloatingLabelAlignment.center'; - } - return 'FloatingLabelAlignment(x: ${x.toStringAsFixed(1)})'; + return switch (x) { + -1.0 => 'FloatingLabelAlignment.start', + 0.0 => 'FloatingLabelAlignment.center', + _ => 'FloatingLabelAlignment(x: ${x.toStringAsFixed(1)})', + }; } @override @@ -1952,7 +1950,7 @@ class _InputDecoratorState extends State with TickerProviderStat } Color _getHoverColor(ThemeData themeData) { - if (decoration.filled == null || !decoration.filled! || isFocused || !decoration.enabled) { + if (decoration.filled == null || !decoration.filled! || !decoration.enabled) { return Colors.transparent; } return decoration.hoverColor ?? themeData.inputDecorationTheme.hoverColor ?? themeData.hoverColor; @@ -2785,8 +2783,7 @@ class InputDecoration { /// The maximum number of lines the [helperText] can occupy. /// - /// Defaults to null, which means that the [helperText] will be limited - /// to a single line with [TextOverflow.ellipsis]. + /// Defaults to null, which means that the [helperText] is not limited. /// /// This value is passed along to the [Text.maxLines] attribute /// of the [Text] widget used to display the helper. @@ -2875,8 +2872,7 @@ class InputDecoration { /// The maximum number of lines the [errorText] can occupy. /// - /// Defaults to null, which means that the [errorText] will be limited - /// to a single line with [TextOverflow.ellipsis]. + /// Defaults to null, which means that the [errorText] is not limited. /// /// This value is passed along to the [Text.maxLines] attribute /// of the [Text] widget used to display the error. diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index bdc3ec7a3cb7..fc3dfb9f7529 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -19,7 +19,7 @@ import 'input_border.dart'; /// See also: /// /// * [WidgetState], a general non-Material version that can be used -/// interchangebly with `MaterialState`. They functionally work the same, +/// interchangeably with `MaterialState`. They functionally work the same, /// except [WidgetState] can be used outside of Material. /// * [MaterialStateProperty], an interface for objects that "resolve" to /// different values depending on a widget's material state. @@ -59,7 +59,7 @@ typedef MaterialState = WidgetState; /// See also: /// /// * [WidgetPropertyResolver], the non-Material form of `MaterialPropertyResolver` -/// that can be used interchangably with `MaterialPropertyResolver. +/// that can be used interchangeably with `MaterialPropertyResolver. @Deprecated( 'Use WidgetPropertyResolver instead. ' 'Moved to the Widgets layer to make code available outside of Material. ' @@ -118,7 +118,7 @@ typedef MaterialPropertyResolver = WidgetPropertyResolver; /// See also /// /// * [WidgetStateColor], the non-Material version that can be used -/// interchangably with `MaterialStateColor`. +/// interchangeably with `MaterialStateColor`. @Deprecated( 'Use WidgetStateColor instead. ' 'Moved to the Widgets layer to make code available outside of Material. ' diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 503286e371c9..3953f9ee833d 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -1022,6 +1022,7 @@ class MenuItemButton extends StatefulWidget { Color? surfaceTintColor, Color? iconColor, TextStyle? textStyle, + Color? overlayColor, double? elevation, EdgeInsetsGeometry? padding, Size? minimumSize, @@ -1047,6 +1048,7 @@ class MenuItemButton extends StatefulWidget { surfaceTintColor: surfaceTintColor, iconColor: iconColor, textStyle: textStyle, + overlayColor: overlayColor, elevation: elevation, padding: padding, minimumSize: minimumSize, @@ -1775,6 +1777,7 @@ class SubmenuButton extends StatefulWidget { Color? surfaceTintColor, Color? iconColor, TextStyle? textStyle, + Color? overlayColor, double? elevation, EdgeInsetsGeometry? padding, Size? minimumSize, @@ -1800,6 +1803,7 @@ class SubmenuButton extends StatefulWidget { surfaceTintColor: surfaceTintColor, iconColor: iconColor, textStyle: textStyle, + overlayColor: overlayColor, elevation: elevation, padding: padding, minimumSize: minimumSize, @@ -2125,37 +2129,22 @@ class _LocalizedShortcutLabeler { keySeparator = '+'; } if (serialized.trigger != null) { - final List modifiers = []; final LogicalKeyboardKey trigger = serialized.trigger!; - if (_usesSymbolicModifiers) { - // macOS/iOS platform convention uses this ordering, with ⌘ always last. - if (serialized.control!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.control, localizations)); - } - if (serialized.alt!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.alt, localizations)); - } - if (serialized.shift!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.shift, localizations)); - } - if (serialized.meta!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.meta, localizations)); - } - } else { - // These should be in this order, to match the LogicalKeySet version. - if (serialized.alt!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.alt, localizations)); - } - if (serialized.control!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.control, localizations)); - } - if (serialized.meta!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.meta, localizations)); - } - if (serialized.shift!) { - modifiers.add(_getModifierLabel(LogicalKeyboardKey.shift, localizations)); - } - } + final List modifiers = [ + if (_usesSymbolicModifiers) ...[ + // MacOS/iOS platform convention uses this ordering, with ⌘ always last. + if (serialized.control!) _getModifierLabel(LogicalKeyboardKey.control, localizations), + if (serialized.alt!) _getModifierLabel(LogicalKeyboardKey.alt, localizations), + if (serialized.shift!) _getModifierLabel(LogicalKeyboardKey.shift, localizations), + if (serialized.meta!) _getModifierLabel(LogicalKeyboardKey.meta, localizations), + ] else ...[ + // This order matches the LogicalKeySet version. + if (serialized.alt!) _getModifierLabel(LogicalKeyboardKey.alt, localizations), + if (serialized.control!) _getModifierLabel(LogicalKeyboardKey.control, localizations), + if (serialized.meta!) _getModifierLabel(LogicalKeyboardKey.meta, localizations), + if (serialized.shift!) _getModifierLabel(LogicalKeyboardKey.shift, localizations), + ], + ]; String? shortcutTrigger; final int logicalKeyId = trigger.keyId; if (_shortcutGraphicEquivalents.containsKey(trigger)) { diff --git a/packages/flutter/lib/src/material/mergeable_material.dart b/packages/flutter/lib/src/material/mergeable_material.dart index 598be315eae1..f027ee02c6a6 100644 --- a/packages/flutter/lib/src/material/mergeable_material.dart +++ b/packages/flutter/lib/src/material/mergeable_material.dart @@ -579,12 +579,10 @@ class _MergeableMaterialState extends State with TickerProvid ); slices = []; - widgets.add( - SizedBox( - width: widget.mainAxis == Axis.horizontal ? _getGapSize(i) : null, - height: widget.mainAxis == Axis.vertical ? _getGapSize(i) : null, - ), - ); + widgets.add(switch (widget.mainAxis) { + Axis.horizontal => SizedBox(width: _getGapSize(i)), + Axis.vertical => SizedBox(height: _getGapSize(i)), + }); } else { final MaterialSlice slice = _children[i] as MaterialSlice; Widget child = slice.child; diff --git a/packages/flutter/lib/src/material/navigation_drawer.dart b/packages/flutter/lib/src/material/navigation_drawer.dart index edd9946c9b7c..bc4cd218286d 100644 --- a/packages/flutter/lib/src/material/navigation_drawer.dart +++ b/packages/flutter/lib/src/material/navigation_drawer.dart @@ -141,7 +141,6 @@ class NavigationDrawer extends StatelessWidget { children.whereType().toList().length; int destinationIndex = 0; - final List wrappedChildren = []; Widget wrapChild(Widget child, int index) => _SelectableAnimatedBuilder( duration: const Duration(milliseconds: 500), isSelected: index == selectedIndex, @@ -162,14 +161,11 @@ class NavigationDrawer extends StatelessWidget { ); }); - for (int i = 0; i < children.length; i++) { - if (children[i] is! NavigationDrawerDestination) { - wrappedChildren.add(children[i]); - } else { - wrappedChildren.add(wrapChild(children[i], destinationIndex)); - destinationIndex += 1; - } - } + final List wrappedChildren = [ + for (final Widget child in children) + if (child is! NavigationDrawerDestination) child + else wrapChild(child, destinationIndex++), + ]; final NavigationDrawerThemeData navigationDrawerTheme = NavigationDrawerTheme.of(context); return Drawer( diff --git a/packages/flutter/lib/src/material/navigation_rail.dart b/packages/flutter/lib/src/material/navigation_rail.dart index 4b4a44d12fdd..731b470a9583 100644 --- a/packages/flutter/lib/src/material/navigation_rail.dart +++ b/packages/flutter/lib/src/material/navigation_rail.dart @@ -346,7 +346,7 @@ class _NavigationRailState extends State with TickerProviderStat late List _destinationControllers; late List> _destinationAnimations; late AnimationController _extendedController; - late Animation _extendedAnimation; + late CurvedAnimation _extendedAnimation; @override void initState() { @@ -488,6 +488,8 @@ class _NavigationRailState extends State with TickerProviderStat controller.dispose(); } _extendedController.dispose(); + _extendedAnimation.dispose(); + } void _initControllers() { @@ -528,8 +530,8 @@ class _NavigationRailState extends State with TickerProviderStat } } -class _RailDestination extends StatelessWidget { - _RailDestination({ +class _RailDestination extends StatefulWidget { + const _RailDestination({ required this.minWidth, required this.minExtendedWidth, required this.icon, @@ -547,11 +549,7 @@ class _RailDestination extends StatelessWidget { this.indicatorColor, this.indicatorShape, this.disabled = false, - }) : _positionAnimation = CurvedAnimation( - parent: ReverseAnimation(destinationAnimation), - curve: Curves.easeInOut, - reverseCurve: Curves.easeInOut.flipped, - ); + }); final double minWidth; final double minExtendedWidth; @@ -571,33 +569,70 @@ class _RailDestination extends StatelessWidget { final ShapeBorder? indicatorShape; final bool disabled; - final Animation _positionAnimation; + + @override + State<_RailDestination> createState() => _RailDestinationState(); +} + +class _RailDestinationState extends State<_RailDestination> { + late CurvedAnimation _positionAnimation; + + @override + void initState() { + super.initState(); + _setPositionAnimation(); + } + + @override + void didUpdateWidget(_RailDestination oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.destinationAnimation != oldWidget.destinationAnimation) { + _positionAnimation.dispose(); + _setPositionAnimation(); + } + } + + void _setPositionAnimation() { + _positionAnimation = CurvedAnimation( + parent: ReverseAnimation(widget.destinationAnimation), + curve: Curves.easeInOut, + reverseCurve: Curves.easeInOut.flipped, + ); + } + + @override + void dispose() { + _positionAnimation.dispose(); + super.dispose(); + } + + @override Widget build(BuildContext context) { assert( - useIndicator || indicatorColor == null, + widget.useIndicator || widget.indicatorColor == null, '[NavigationRail.indicatorColor] does not have an effect when [NavigationRail.useIndicator] is false', ); final ThemeData theme = Theme.of(context); final TextDirection textDirection = Directionality.of(context); final bool material3 = theme.useMaterial3; - final EdgeInsets destinationPadding = (padding ?? EdgeInsets.zero).resolve(textDirection); + final EdgeInsets destinationPadding = (widget.padding ?? EdgeInsets.zero).resolve(textDirection); Offset indicatorOffset; bool applyXOffset = false; final Widget themedIcon = IconTheme( - data: disabled - ? iconTheme.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.38)) - : iconTheme, - child: icon, + data: widget.disabled + ? widget.iconTheme.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.38)) + : widget.iconTheme, + child: widget.icon, ); final Widget styledLabel = DefaultTextStyle( - style: disabled - ? labelTextStyle.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.38)) - : labelTextStyle, - child: label, + style: widget.disabled + ? widget.labelTextStyle.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.38)) + : widget.labelTextStyle, + child: widget.label, ); Widget content; @@ -605,30 +640,30 @@ class _RailDestination extends StatelessWidget { // The indicator height is fixed and equal to _kIndicatorHeight. // When the icon height is larger than the indicator height the indicator // vertical offset is used to vertically center the indicator. - final bool isLargeIconSize = iconTheme.size != null && iconTheme.size! > _kIndicatorHeight; - final double indicatorVerticalOffset = isLargeIconSize ? (iconTheme.size! - _kIndicatorHeight) / 2 : 0; + final bool isLargeIconSize = widget.iconTheme.size != null && widget.iconTheme.size! > _kIndicatorHeight; + final double indicatorVerticalOffset = isLargeIconSize ? (widget.iconTheme.size! - _kIndicatorHeight) / 2 : 0; - switch (labelType) { + switch (widget.labelType) { case NavigationRailLabelType.none: // Split the destination spacing across the top and bottom to keep the icon centered. final Widget? spacing = material3 ? const SizedBox(height: _verticalDestinationSpacingM3 / 2) : null; indicatorOffset = Offset( - minWidth / 2 + destinationPadding.left, + widget.minWidth / 2 + destinationPadding.left, _verticalDestinationSpacingM3 / 2 + destinationPadding.top + indicatorVerticalOffset, ); final Widget iconPart = Column( children: [ if (spacing != null) spacing, SizedBox( - width: minWidth, - height: material3 ? null : minWidth, + width: widget.minWidth, + height: material3 ? null : widget.minWidth, child: Center( child: _AddIndicator( - addIndicator: useIndicator, - indicatorColor: indicatorColor, - indicatorShape: indicatorShape, + addIndicator: widget.useIndicator, + indicatorColor: widget.indicatorColor, + indicatorShape: widget.indicatorShape, isCircular: !material3, - indicatorAnimation: destinationAnimation, + indicatorAnimation: widget.destinationAnimation, child: themedIcon, ), ), @@ -636,9 +671,9 @@ class _RailDestination extends StatelessWidget { if (spacing != null) spacing, ], ); - if (extendedTransitionAnimation.value == 0) { + if (widget.extendedTransitionAnimation.value == 0) { content = Padding( - padding: padding ?? EdgeInsets.zero, + padding: widget.padding ?? EdgeInsets.zero, child: Stack( children: [ iconPart, @@ -646,20 +681,20 @@ class _RailDestination extends StatelessWidget { SizedBox.shrink( child: Visibility.maintain( visible: false, - child: label, + child: widget.label, ), ), ], ), ); } else { - final Animation labelFadeAnimation = extendedTransitionAnimation.drive(CurveTween(curve: const Interval(0.0, 0.25))); + final Animation labelFadeAnimation = widget.extendedTransitionAnimation.drive(CurveTween(curve: const Interval(0.0, 0.25))); applyXOffset = true; content = Padding( - padding: padding ?? EdgeInsets.zero, + padding: widget.padding ?? EdgeInsets.zero, child: ConstrainedBox( constraints: BoxConstraints( - minWidth: lerpDouble(minWidth, minExtendedWidth, extendedTransitionAnimation.value)!, + minWidth: lerpDouble(widget.minWidth, widget.minExtendedWidth, widget.extendedTransitionAnimation.value)!, ), child: ClipRect( child: Row( @@ -667,7 +702,7 @@ class _RailDestination extends StatelessWidget { iconPart, Align( heightFactor: 1.0, - widthFactor: extendedTransitionAnimation.value, + widthFactor: widget.extendedTransitionAnimation.value, alignment: AlignmentDirectional.centerStart, child: FadeTransition( alwaysIncludeSemantics: true, @@ -675,7 +710,7 @@ class _RailDestination extends StatelessWidget { child: styledLabel, ), ), - SizedBox(width: _horizontalDestinationPadding * extendedTransitionAnimation.value), + SizedBox(width: _horizontalDestinationPadding * widget.extendedTransitionAnimation.value), ], ), ), @@ -685,30 +720,30 @@ class _RailDestination extends StatelessWidget { case NavigationRailLabelType.selected: final double appearingAnimationValue = 1 - _positionAnimation.value; final double verticalPadding = lerpDouble(_verticalDestinationPaddingNoLabel, _verticalDestinationPaddingWithLabel, appearingAnimationValue)!; - final Interval interval = selected ? const Interval(0.25, 0.75) : const Interval(0.75, 1.0); - final Animation labelFadeAnimation = destinationAnimation.drive(CurveTween(curve: interval)); - final double minHeight = material3 ? 0 : minWidth; + final Interval interval = widget.selected ? const Interval(0.25, 0.75) : const Interval(0.75, 1.0); + final Animation labelFadeAnimation = widget.destinationAnimation.drive(CurveTween(curve: interval)); + final double minHeight = material3 ? 0 : widget.minWidth; final Widget topSpacing = SizedBox(height: material3 ? 0 : verticalPadding); final Widget labelSpacing = SizedBox(height: material3 ? lerpDouble(0, _verticalIconLabelSpacingM3, appearingAnimationValue)! : 0); final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : verticalPadding); final double indicatorHorizontalPadding = (destinationPadding.left / 2) - (destinationPadding.right / 2); final double indicatorVerticalPadding = destinationPadding.top; indicatorOffset = Offset( - minWidth / 2 + indicatorHorizontalPadding, + widget.minWidth / 2 + indicatorHorizontalPadding, indicatorVerticalPadding + indicatorVerticalOffset, ); - if (minWidth < _NavigationRailDefaultsM2(context).minWidth!) { + if (widget.minWidth < _NavigationRailDefaultsM2(context).minWidth!) { indicatorOffset = Offset( - minWidth / 2 + _horizontalDestinationSpacingM3, + widget.minWidth / 2 + _horizontalDestinationSpacingM3, indicatorVerticalPadding + indicatorVerticalOffset, ); } content = Container( constraints: BoxConstraints( - minWidth: minWidth, + minWidth: widget.minWidth, minHeight: minHeight, ), - padding: padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding), + padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding), child: ClipRect( child: Column( mainAxisSize: MainAxisSize.min, @@ -716,11 +751,11 @@ class _RailDestination extends StatelessWidget { children: [ topSpacing, _AddIndicator( - addIndicator: useIndicator, - indicatorColor: indicatorColor, - indicatorShape: indicatorShape, + addIndicator: widget.useIndicator, + indicatorColor: widget.indicatorColor, + indicatorShape: widget.indicatorShape, isCircular: false, - indicatorAnimation: destinationAnimation, + indicatorAnimation: widget.destinationAnimation, child: themedIcon, ), labelSpacing, @@ -740,37 +775,37 @@ class _RailDestination extends StatelessWidget { ), ); case NavigationRailLabelType.all: - final double minHeight = material3 ? 0 : minWidth; + final double minHeight = material3 ? 0 : widget.minWidth; final Widget topSpacing = SizedBox(height: material3 ? 0 : _verticalDestinationPaddingWithLabel); final Widget labelSpacing = SizedBox(height: material3 ? _verticalIconLabelSpacingM3 : 0); final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : _verticalDestinationPaddingWithLabel); final double indicatorHorizontalPadding = (destinationPadding.left / 2) - (destinationPadding.right / 2); final double indicatorVerticalPadding = destinationPadding.top; indicatorOffset = Offset( - minWidth / 2 + indicatorHorizontalPadding, + widget.minWidth / 2 + indicatorHorizontalPadding, indicatorVerticalPadding + indicatorVerticalOffset, ); - if (minWidth < _NavigationRailDefaultsM2(context).minWidth!) { + if (widget.minWidth < _NavigationRailDefaultsM2(context).minWidth!) { indicatorOffset = Offset( - minWidth / 2 + _horizontalDestinationSpacingM3, + widget.minWidth / 2 + _horizontalDestinationSpacingM3, indicatorVerticalPadding + indicatorVerticalOffset, ); } content = Container( constraints: BoxConstraints( - minWidth: minWidth, + minWidth: widget.minWidth, minHeight: minHeight, ), - padding: padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding), + padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding), child: Column( children: [ topSpacing, _AddIndicator( - addIndicator: useIndicator, - indicatorColor: indicatorColor, - indicatorShape: indicatorShape, + addIndicator: widget.useIndicator, + indicatorColor: widget.indicatorColor, + indicatorShape: widget.indicatorShape, isCircular: false, - indicatorAnimation: destinationAnimation, + indicatorAnimation: widget.destinationAnimation, child: themedIcon, ), labelSpacing, @@ -791,15 +826,15 @@ class _RailDestination extends StatelessWidget { : colors.primary.withOpacity(0.04); return Semantics( container: true, - selected: selected, + selected: widget.selected, child: Stack( children: [ Material( type: MaterialType.transparency, child: _IndicatorInkWell( - onTap: disabled ? null : onTap, - borderRadius: BorderRadius.all(Radius.circular(minWidth / 2.0)), - customBorder: indicatorShape, + onTap: widget.disabled ? null : widget.onTap, + borderRadius: BorderRadius.all(Radius.circular(widget.minWidth / 2.0)), + customBorder: widget.indicatorShape, splashColor: effectiveSplashColor, hoverColor: effectiveHoverColor, useMaterial3: material3, @@ -810,7 +845,7 @@ class _RailDestination extends StatelessWidget { ), ), Semantics( - label: indexLabel, + label: widget.indexLabel, ), ], ), diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 0dad9a9d00a6..87409aa1a1e2 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -572,7 +572,7 @@ class _CheckedPopupMenuItemState extends PopupMenuItemState extends StatelessWidget { +class _PopupMenu extends StatefulWidget { const _PopupMenu({ super.key, required this.itemKeys, @@ -589,22 +589,68 @@ class _PopupMenu extends StatelessWidget { final Clip clipBehavior; @override - Widget build(BuildContext context) { - final double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. - final List children = []; - final ThemeData theme = Theme.of(context); - final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); - final PopupMenuThemeData defaults = theme.useMaterial3 ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context); + State<_PopupMenu> createState() => _PopupMenuState(); +} + +class _PopupMenuState extends State<_PopupMenu> { + List _opacities = const []; + + @override + void initState() { + super.initState(); + _setOpacities(); + } + + @override + void didUpdateWidget(covariant _PopupMenu oldWidget) { + super.didUpdateWidget(oldWidget); + if ( + oldWidget.route.items.length != widget.route.items.length || + oldWidget.route.animation != widget.route.animation + ) { + _setOpacities(); + } + + } - for (int i = 0; i < route.items.length; i += 1) { + void _setOpacities() { + for (final CurvedAnimation opacity in _opacities) { + opacity.dispose(); + } + final List newOpacities = []; + final double unit = 1.0 / (widget.route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. + for (int i = 0; i < widget.route.items.length; i += 1) { final double start = (i + 1) * unit; final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( - parent: route.animation!, + parent: widget.route.animation!, curve: Interval(start, end), ); - Widget item = route.items[i]; - if (route.initialValue != null && route.items[i].represents(route.initialValue)) { + newOpacities.add(opacity); + } + _opacities = newOpacities; + } + + @override + void dispose() { + for (final CurvedAnimation opacity in _opacities) { + opacity.dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final double unit = 1.0 / (widget.route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. + final List children = []; + final ThemeData theme = Theme.of(context); + final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); + final PopupMenuThemeData defaults = theme.useMaterial3 ? _PopupMenuDefaultsM3(context) : _PopupMenuDefaultsM2(context); + + for (int i = 0; i < widget.route.items.length; i += 1) { + final CurvedAnimation opacity = _opacities[i]; + Widget item = widget.route.items[i]; + if (widget.route.initialValue != null && widget.route.items[i].represents(widget.route.initialValue)) { item = ColoredBox( color: Theme.of(context).highlightColor, child: item, @@ -613,10 +659,10 @@ class _PopupMenu extends StatelessWidget { children.add( _MenuItem( onLayout: (Size size) { - route.itemSizes[i] = size; + widget.route.itemSizes[i] = size; }, child: FadeTransition( - key: itemKeys[i], + key: widget.itemKeys[i], opacity: opacity, child: item, ), @@ -626,10 +672,10 @@ class _PopupMenu extends StatelessWidget { final CurveTween opacity = CurveTween(curve: const Interval(0.0, 1.0 / 3.0)); final CurveTween width = CurveTween(curve: Interval(0.0, unit)); - final CurveTween height = CurveTween(curve: Interval(0.0, unit * route.items.length)); + final CurveTween height = CurveTween(curve: Interval(0.0, unit * widget.route.items.length)); final Widget child = ConstrainedBox( - constraints: constraints ?? const BoxConstraints( + constraints: widget.constraints ?? const BoxConstraints( minWidth: _kMenuMinWidth, maxWidth: _kMenuMaxWidth, ), @@ -639,7 +685,7 @@ class _PopupMenu extends StatelessWidget { scopesRoute: true, namesRoute: true, explicitChildNodes: true, - label: semanticLabel, + label: widget.semanticLabel, child: SingleChildScrollView( padding: const EdgeInsets.symmetric( vertical: _kMenuVerticalPadding, @@ -651,22 +697,22 @@ class _PopupMenu extends StatelessWidget { ); return AnimatedBuilder( - animation: route.animation!, + animation: widget.route.animation!, builder: (BuildContext context, Widget? child) { return FadeTransition( - opacity: opacity.animate(route.animation!), + opacity: opacity.animate(widget.route.animation!), child: Material( - shape: route.shape ?? popupMenuTheme.shape ?? defaults.shape, - color: route.color ?? popupMenuTheme.color ?? defaults.color, - clipBehavior: clipBehavior, + shape: widget.route.shape ?? popupMenuTheme.shape ?? defaults.shape, + color: widget.route.color ?? popupMenuTheme.color ?? defaults.color, + clipBehavior: widget.clipBehavior, type: MaterialType.card, - elevation: route.elevation ?? popupMenuTheme.elevation ?? defaults.elevation!, - shadowColor: route.shadowColor ?? popupMenuTheme.shadowColor ?? defaults.shadowColor, - surfaceTintColor: route.surfaceTintColor ?? popupMenuTheme.surfaceTintColor ?? defaults.surfaceTintColor, + elevation: widget.route.elevation ?? popupMenuTheme.elevation ?? defaults.elevation!, + shadowColor: widget.route.shadowColor ?? popupMenuTheme.shadowColor ?? defaults.shadowColor, + surfaceTintColor: widget.route.surfaceTintColor ?? popupMenuTheme.surfaceTintColor ?? defaults.surfaceTintColor, child: Align( alignment: AlignmentDirectional.topEnd, - widthFactor: width.evaluate(route.animation!), - heightFactor: height.evaluate(route.animation!), + widthFactor: width.evaluate(widget.route.animation!), + heightFactor: height.evaluate(widget.route.animation!), child: child, ), ), diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index 31ca2db16726..3b05f6ac8472 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -1230,11 +1230,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix // a tap, it consists of a call to onChangeStart with the previous value and // a call to onChangeEnd with the new value. final RangeValues currentValues = _discretizeRangeValues(values); - if (_lastThumbSelection == Thumb.start) { - _newValues = RangeValues(tapValue, currentValues.end); - } else if (_lastThumbSelection == Thumb.end) { - _newValues = RangeValues(currentValues.start, tapValue); - } + _newValues = switch (_lastThumbSelection!) { + Thumb.start => RangeValues(tapValue, currentValues.end), + Thumb.end => RangeValues(currentValues.start, tapValue), + }; _updateLabelPainter(_lastThumbSelection!); onChangeStart?.call(currentValues); @@ -1286,11 +1285,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } final double currentDragValue = _discretize(dragValue); - if (_lastThumbSelection == Thumb.start) { - _newValues = RangeValues(math.min(currentDragValue, currentValues.end - _minThumbSeparationValue), currentValues.end); - } else if (_lastThumbSelection == Thumb.end) { - _newValues = RangeValues(currentValues.start, math.max(currentDragValue, currentValues.start + _minThumbSeparationValue)); - } + _newValues = switch (_lastThumbSelection!) { + Thumb.start => RangeValues(math.min(currentDragValue, currentValues.end - _minThumbSeparationValue), currentValues.end), + Thumb.end => RangeValues(currentValues.start, math.max(currentDragValue, currentValues.start + _minThumbSeparationValue)), + }; onChanged!(_newValues); } } diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index ba639de35f1f..cbfcb2dfed2e 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -203,7 +203,12 @@ class SegmentedButton extends StatefulWidget { /// /// The [foregroundColor], [selectedForegroundColor], and [disabledForegroundColor] /// colors are used to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], - /// and a derived [ButtonStyle.overlayColor]. + /// and a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. + /// + /// If [overlayColor] is specified and its value is [Colors.transparent] + /// then the pressed/focused/hovered highlights are effectively defeated. + /// Otherwise a [MaterialStateProperty] with the same opacities as the + /// default is created. /// /// The [backgroundColor], [selectedBackgroundColor] and [disabledBackgroundColor] /// colors are used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. @@ -261,6 +266,7 @@ class SegmentedButton extends StatefulWidget { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, + Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -286,9 +292,13 @@ class SegmentedButton extends StatefulWidget { (backgroundColor == null && disabledBackgroundColor == null && selectedBackgroundColor == null) ? null : _SegmentButtonDefaultColor(backgroundColor, disabledBackgroundColor, selectedBackgroundColor); - final MaterialStateProperty? overlayColor = (foregroundColor == null && selectedForegroundColor == null) - ? null - : _SegmentedButtonDefaultsM3.resolveStateColor(foregroundColor, selectedForegroundColor); + final MaterialStateProperty? overlayColorProp = (foregroundColor == null && + selectedForegroundColor == null && overlayColor == null) + ? null + : switch (overlayColor) { + (final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), + _ => _SegmentedButtonDefaultsM3.resolveStateColor(foregroundColor, selectedForegroundColor, overlayColor), + }; return TextButton.styleFrom( textStyle: textStyle, shadowColor: shadowColor, @@ -311,7 +321,7 @@ class SegmentedButton extends StatefulWidget { ).copyWith( foregroundColor: foregroundColorProp, backgroundColor: backgroundColorProp, - overlayColor: overlayColor, + overlayColor: overlayColorProp, ); } @@ -1066,27 +1076,27 @@ class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData { @override Widget? get selectedIcon => const Icon(Icons.check); - static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor){ + static MaterialStateProperty resolveStateColor(Color? unselectedColor, Color? selectedColor, Color? overlayColor){ return MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.pressed)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return selectedColor?.withOpacity(0.08); + return (overlayColor ?? selectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return selectedColor?.withOpacity(0.1); + return (overlayColor ?? selectedColor)?.withOpacity(0.1); } } else { if (states.contains(MaterialState.pressed)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } if (states.contains(MaterialState.hovered)) { - return unselectedColor?.withOpacity(0.08); + return (overlayColor ?? unselectedColor)?.withOpacity(0.08); } if (states.contains(MaterialState.focused)) { - return unselectedColor?.withOpacity(0.1); + return (overlayColor ?? unselectedColor)?.withOpacity(0.1); } } return Colors.transparent; diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index 90a4ad7f4146..f546b7196d2f 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -117,12 +117,10 @@ class _SelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestur final Offset editableOffset = renderEditable.maxLines == 1 ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) : Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset); - final double effectiveScrollPosition = _scrollPosition - _dragStartScrollOffset; - final bool scrollingOnVerticalAxis = _scrollDirection == AxisDirection.up || _scrollDirection == AxisDirection.down; - final Offset scrollableOffset = Offset( - !scrollingOnVerticalAxis ? effectiveScrollPosition : 0.0, - scrollingOnVerticalAxis ? effectiveScrollPosition : 0.0, - ); + final Offset scrollableOffset = switch (axisDirectionToAxis(_scrollDirection ?? AxisDirection.left)) { + Axis.horizontal => Offset(_scrollPosition - _dragStartScrollOffset, 0), + Axis.vertical => Offset(0, _scrollPosition - _dragStartScrollOffset), + }; renderEditable.selectWordsInRange( from: details.globalPosition - details.offsetFromOrigin - editableOffset - scrollableOffset, to: details.globalPosition, diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index f22c33d9b1ae..dd979843374b 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -1619,20 +1619,18 @@ class RectangularSliderTrackShape extends SliderTrackShape with BaseSliderTrackS context.canvas.drawRect(rightTrackSegment, rightTrackPaint); } - final bool showSecondaryTrack = (secondaryOffset != null) && - ((textDirection == TextDirection.ltr) - ? (secondaryOffset.dx > thumbCenter.dx) - : (secondaryOffset.dx < thumbCenter.dx)); + final bool showSecondaryTrack = secondaryOffset != null && switch (textDirection) { + TextDirection.rtl => secondaryOffset.dx < thumbCenter.dx, + TextDirection.ltr => secondaryOffset.dx > thumbCenter.dx, + }; if (showSecondaryTrack) { final ColorTween secondaryTrackColorTween = ColorTween(begin: sliderTheme.disabledSecondaryActiveTrackColor, end: sliderTheme.secondaryActiveTrackColor); final Paint secondaryTrackPaint = Paint()..color = secondaryTrackColorTween.evaluate(enableAnimation)!; - final Rect secondaryTrackSegment = Rect.fromLTRB( - (textDirection == TextDirection.ltr) ? thumbCenter.dx : secondaryOffset.dx, - trackRect.top, - (textDirection == TextDirection.ltr) ? secondaryOffset.dx : thumbCenter.dx, - trackRect.bottom, - ); + final Rect secondaryTrackSegment = switch (textDirection) { + TextDirection.rtl => Rect.fromLTRB(secondaryOffset.dx, trackRect.top, thumbCenter.dx, trackRect.bottom), + TextDirection.ltr => Rect.fromLTRB(thumbCenter.dx, trackRect.top, secondaryOffset.dx, trackRect.bottom), + }; if (!secondaryTrackSegment.isEmpty) { context.canvas.drawRect(secondaryTrackSegment, secondaryTrackPaint); } diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 35a3dae5507e..945bdb23d963 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -1066,9 +1066,14 @@ class _SwitchPainter extends ToggleablePainter { return; } _positionController = value; + _colorAnimation?.dispose(); + _colorAnimation = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn); notifyListeners(); } + CurvedAnimation? _colorAnimation; + + Icon? get activeIcon => _activeIcon; Icon? _activeIcon; set activeIcon(Icon? value) { @@ -1516,7 +1521,7 @@ class _SwitchPainter extends ToggleablePainter { final double inset = thumbOffset == null ? 0 : 1.0 - (currentValue - thumbOffset!).abs() * 2.0; thumbSize = Size(thumbSize!.width - inset, thumbSize.height - inset); - final double colorValue = CurvedAnimation(parent: positionController, curve: Curves.easeOut, reverseCurve: Curves.easeIn).value; + final double colorValue = _colorAnimation!.value; final Color trackColor = Color.lerp(inactiveTrackColor, activeTrackColor, colorValue)!; final Color? trackOutlineColor = inactiveTrackOutlineColor == null || activeTrackOutlineColor == null ? null : Color.lerp(inactiveTrackOutlineColor, activeTrackOutlineColor, colorValue); @@ -1737,6 +1742,7 @@ class _SwitchPainter extends ToggleablePainter { _cachedThumbColor = null; _cachedThumbImage = null; _cachedThumbErrorListener = null; + _colorAnimation?.dispose(); super.dispose(); } } diff --git a/packages/flutter/lib/src/material/tab_controller.dart b/packages/flutter/lib/src/material/tab_controller.dart index 515ab907b249..f9dd965bcb99 100644 --- a/packages/flutter/lib/src/material/tab_controller.dart +++ b/packages/flutter/lib/src/material/tab_controller.dart @@ -140,13 +140,15 @@ class TabController extends ChangeNotifier { /// Creates a new [TabController] with `index`, `previousIndex`, `length`, and - /// `animationDuration` if they are non-null. + /// `animationDuration` if they are non-null, and disposes current instance. /// /// This method is used by [DefaultTabController]. /// /// When [DefaultTabController.length] is updated, this method is called to /// create a new [TabController] without creating a new [AnimationController]. - TabController _copyWith({ + /// Instead the [_animationController] is nulled in current instance and + /// passed to the new instance. + TabController _copyWithAndDispose({ required int? index, required int? length, required int? previousIndex, @@ -155,13 +157,16 @@ class TabController extends ChangeNotifier { if (index != null) { _animationController!.value = index.toDouble(); } - return TabController._( + final TabController result = TabController._( index: index ?? _index, length: length ?? this.length, animationController: _animationController, previousIndex: previousIndex ?? _previousIndex, animationDuration: animationDuration ?? _animationDuration, ); + _animationController = null; + dispose(); + return result; } /// An animation whose value represents the current position of the [TabBar]'s @@ -489,7 +494,7 @@ class _DefaultTabControllerState extends State with Single newIndex = math.max(0, widget.length - 1); previousIndex = _controller.index; } - _controller = _controller._copyWith( + _controller = _controller._copyWithAndDispose( length: widget.length, animationDuration: widget.animationDuration, index: newIndex, @@ -498,7 +503,7 @@ class _DefaultTabControllerState extends State with Single } if (oldWidget.animationDuration != widget.animationDuration) { - _controller = _controller._copyWith( + _controller = _controller._copyWithAndDispose( length: widget.length, animationDuration: widget.animationDuration, index: _controller.index, diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 7917707cc7f0..6cb60b377914 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -442,6 +442,7 @@ class _IndicatorPainter extends CustomPainter { this.dividerColor, this.dividerHeight, required this.showDivider, + this.devicePixelRatio, }) : super(repaint: controller.animation) { // TODO(polina-c): stop duplicating code across disposables // https://github.com/flutter/flutter/issues/137435 @@ -466,6 +467,7 @@ class _IndicatorPainter extends CustomPainter { final Color? dividerColor; final double? dividerHeight; final bool showDivider; + final double? devicePixelRatio; // _currentTabOffsets and _currentTextDirection are set each time TabBar // layout is completed. These values can be null when TabBar contains no @@ -562,6 +564,7 @@ class _IndicatorPainter extends CustomPainter { final ImageConfiguration configuration = ImageConfiguration( size: _currentRect!.size, textDirection: _currentTextDirection, + devicePixelRatio: devicePixelRatio, ); if (showDivider && dividerHeight !> 0) { final Paint dividerPaint = Paint()..color = dividerColor!..strokeWidth = dividerHeight!; @@ -1433,6 +1436,7 @@ class _TabBarState extends State { dividerColor: widget.dividerColor ?? tabBarTheme.dividerColor ?? _defaults.dividerColor, dividerHeight: widget.dividerHeight ?? tabBarTheme.dividerHeight ?? _defaults.dividerHeight, showDivider: theme.useMaterial3 && !widget.isScrollable, + devicePixelRatio: MediaQuery.devicePixelRatioOf(context), ); oldPainter?.dispose(); @@ -1532,18 +1536,14 @@ class _TabBarState extends State { final double index = _controller!.index.toDouble(); final double value = _controller!.animation!.value; - final double offset; - if (value == index - 1.0) { - offset = leadingPosition ?? middlePosition; - } else if (value == index + 1.0) { - offset = trailingPosition ?? middlePosition; - } else if (value == index) { - offset = middlePosition; - } else if (value < index) { - offset = leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value)!; - } else { - offset = trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index)!; - } + final double offset = switch (value - index) { + -1.0 || 1.0 => leadingPosition ?? middlePosition, + 0 => middlePosition, + < 0 when leadingPosition == null => middlePosition, + > 0 when trailingPosition == null => middlePosition, + < 0 => lerpDouble(middlePosition, leadingPosition, index - value)!, + _ => lerpDouble(middlePosition, trailingPosition, value - index)!, + }; _scrollController!.jumpTo(offset); } diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart index 557612905c5f..ab81f7fd8ca7 100644 --- a/packages/flutter/lib/src/material/toggle_buttons.dart +++ b/packages/flutter/lib/src/material/toggle_buttons.dart @@ -738,26 +738,16 @@ class ToggleButtons extends StatelessWidget { ?? theme.textTheme.bodyMedium!; final BoxConstraints? currentConstraints = constraints ?? toggleButtonsTheme.constraints; - final Size minimumSize = currentConstraints == null - ? const Size.square(kMinInteractiveDimension) - : Size(currentConstraints.minWidth, currentConstraints.minHeight); - final Size? maximumSize = currentConstraints == null - ? null - : Size(currentConstraints.maxWidth, currentConstraints.maxHeight); + final Size minimumSize = currentConstraints?.smallest + ?? const Size.square(kMinInteractiveDimension); + final Size? maximumSize = currentConstraints?.biggest; final Size minPaddingSize; switch (tapTargetSize ?? theme.materialTapTargetSize) { case MaterialTapTargetSize.padded: - if (direction == Axis.horizontal) { - minPaddingSize = const Size( - 0.0, - kMinInteractiveDimension, - ); - } else { - minPaddingSize = const Size( - kMinInteractiveDimension, - 0.0, - ); - } + minPaddingSize = switch (direction) { + Axis.horizontal => const Size(0.0, kMinInteractiveDimension), + Axis.vertical => const Size(kMinInteractiveDimension, 0.0), + }; assert(minPaddingSize.width >= 0.0); assert(minPaddingSize.height >= 0.0); case MaterialTapTargetSize.shrinkWrap: @@ -1658,12 +1648,10 @@ class _RenderInputPadding extends RenderShiftedBox { } // Only adjust one axis to ensure the correct button is tapped. - Offset center; - if (direction == Axis.horizontal) { - center = Offset(position.dx, child!.size.height / 2); - } else { - center = Offset(child!.size.width / 2, position.dy); - } + final Offset center = switch (direction) { + Axis.horizontal => Offset(position.dx, child!.size.height / 2), + Axis.vertical => Offset(child!.size.width / 2, position.dy), + }; return result.addWithRawTransform( transform: MatrixUtils.forceToPoint(center), position: center, diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 4431019283dc..1456e0d278db 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -455,6 +455,13 @@ class TooltipState extends State with SingleTickerProviderStateMixin { vsync: this, )..addStatusListener(_handleStatusChanged); } + CurvedAnimation? _backingOverlayAnimation; + CurvedAnimation get _overlayAnimation { + return _backingOverlayAnimation ??= CurvedAnimation( + parent: _controller, + curve: Curves.fastOutSlowIn, + ); + } LongPressGestureRecognizer? _longPressRecognizer; TapGestureRecognizer? _tapRecognizer; @@ -796,7 +803,7 @@ class TooltipState extends State with SingleTickerProviderStateMixin { decoration: widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration, textStyle: widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle, textAlign: widget.textAlign ?? tooltipTheme.textAlign ?? _defaultTextAlign, - animation: CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn), + animation:_overlayAnimation, target: target, verticalOffset: widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset, preferBelow: widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow, @@ -821,6 +828,7 @@ class TooltipState extends State with SingleTickerProviderStateMixin { _tapRecognizer?.dispose(); _timer?.cancel(); _backingController?.dispose(); + _backingOverlayAnimation?.dispose(); super.dispose(); } diff --git a/packages/flutter/lib/src/material/user_accounts_drawer_header.dart b/packages/flutter/lib/src/material/user_accounts_drawer_header.dart index 7550a00e1cb7..60aee2c0a5b0 100644 --- a/packages/flutter/lib/src/material/user_accounts_drawer_header.dart +++ b/packages/flutter/lib/src/material/user_accounts_drawer_header.dart @@ -87,8 +87,8 @@ class _AccountDetails extends StatefulWidget { } class _AccountDetailsState extends State<_AccountDetails> with SingleTickerProviderStateMixin { - late Animation _animation; - late AnimationController _controller; + late final CurvedAnimation _animation; + late final AnimationController _controller; @override void initState () { super.initState(); @@ -110,6 +110,7 @@ class _AccountDetailsState extends State<_AccountDetails> with SingleTickerProvi @override void dispose() { _controller.dispose(); + _animation.dispose(); super.dispose(); } diff --git a/packages/flutter/lib/src/painting/box_border.dart b/packages/flutter/lib/src/painting/box_border.dart index 0155ac9deaae..e41e926d0a11 100644 --- a/packages/flutter/lib/src/painting/box_border.dart +++ b/packages/flutter/lib/src/painting/box_border.dart @@ -492,20 +492,12 @@ class Border extends BoxBorder { } Set _distinctVisibleColors() { - final Set distinctVisibleColors = {}; - if (top.style != BorderStyle.none) { - distinctVisibleColors.add(top.color); - } - if (right.style != BorderStyle.none) { - distinctVisibleColors.add(right.color); - } - if (bottom.style != BorderStyle.none) { - distinctVisibleColors.add(bottom.color); - } - if (left.style != BorderStyle.none) { - distinctVisibleColors.add(left.color); - } - return distinctVisibleColors; + return { + if (top.style != BorderStyle.none) top.color, + if (right.style != BorderStyle.none) right.color, + if (bottom.style != BorderStyle.none) bottom.color, + if (left.style != BorderStyle.none) left.color, + }; } // [BoxBorder.paintNonUniformBorder] is about 20% faster than [paintBorder], @@ -840,21 +832,12 @@ class BorderDirectional extends BoxBorder { } Set _distinctVisibleColors() { - final Set distinctVisibleColors = {}; - if (top.style != BorderStyle.none) { - distinctVisibleColors.add(top.color); - } - if (end.style != BorderStyle.none) { - distinctVisibleColors.add(end.color); - } - if (bottom.style != BorderStyle.none) { - distinctVisibleColors.add(bottom.color); - } - if (start.style != BorderStyle.none) { - distinctVisibleColors.add(start.color); - } - - return distinctVisibleColors; + return { + if (top.style != BorderStyle.none) top.color, + if (end.style != BorderStyle.none) end.color, + if (bottom.style != BorderStyle.none) bottom.color, + if (start.style != BorderStyle.none) start.color, + }; } diff --git a/packages/flutter/lib/src/painting/colors.dart b/packages/flutter/lib/src/painting/colors.dart index f5c9e06bd885..52210e98ed2d 100644 --- a/packages/flutter/lib/src/painting/colors.dart +++ b/packages/flutter/lib/src/painting/colors.dart @@ -31,34 +31,14 @@ Color _colorFromHue( double secondary, double match, ) { - double red; - double green; - double blue; - if (hue < 60.0) { - red = chroma; - green = secondary; - blue = 0.0; - } else if (hue < 120.0) { - red = secondary; - green = chroma; - blue = 0.0; - } else if (hue < 180.0) { - red = 0.0; - green = chroma; - blue = secondary; - } else if (hue < 240.0) { - red = 0.0; - green = secondary; - blue = chroma; - } else if (hue < 300.0) { - red = secondary; - green = 0.0; - blue = chroma; - } else { - red = chroma; - green = 0.0; - blue = secondary; - } + final (double red, double green, double blue) = switch (hue) { + < 60.0 => (chroma, secondary, 0.0), + < 120.0 => (secondary, chroma, 0.0), + < 180.0 => (0.0, chroma, secondary), + < 240.0 => (0.0, secondary, chroma), + < 300.0 => (secondary, 0.0, chroma), + _ => (chroma, 0.0, secondary), + }; return Color.fromARGB((alpha * 0xFF).round(), ((red + match) * 0xFF).round(), ((green + match) * 0xFF).round(), ((blue + match) * 0xFF).round()); } diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index ecbdffd06164..667c7474c01d 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -330,17 +330,11 @@ class _FlutterLogoPainter extends BoxPainter { if (canvasSize.isEmpty) { return; } - final Size logoSize; - if (_config._position > 0.0) { - // horizontal style - logoSize = const Size(820.0, 232.0); - } else if (_config._position < 0.0) { - // stacked style - logoSize = const Size(252.0, 306.0); - } else { - // only the mark - logoSize = const Size(202.0, 202.0); - } + final Size logoSize = switch (_config._position) { + > 0.0 => const Size(820.0, 232.0), // horizontal style + < 0.0 => const Size(252.0, 306.0), // stacked style + _ => const Size(202.0, 202.0), // only the mark + }; final FittedSizes fittedSize = applyBoxFit(BoxFit.contain, logoSize, canvasSize); assert(fittedSize.source == logoSize); final Rect rect = Alignment.center.inscribe(fittedSize.destination, offset & canvasSize); diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 18a8b6110f81..2bf0faca415c 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -1470,13 +1470,13 @@ class TextPainter { .getBoxesForRange(graphemeRange.start, graphemeRange.end, boxHeightStyle: ui.BoxHeightStyle.strut); if (boxes.isNotEmpty) { - final bool ahchorToLeft = switch (glyphInfo.writingDirection) { + final bool anchorToLeft = switch (glyphInfo.writingDirection) { TextDirection.ltr => anchorToLeadingEdge, TextDirection.rtl => !anchorToLeadingEdge, }; - final TextBox box = ahchorToLeft ? boxes.first : boxes.last; + final TextBox box = anchorToLeft ? boxes.first : boxes.last; metrics = _LineCaretMetrics( - offset: Offset(ahchorToLeft ? box.left : box.right, box.top), + offset: Offset(anchorToLeft ? box.left : box.right, box.top), writingDirection: box.direction, ); } else { diff --git a/packages/flutter/lib/src/physics/spring_simulation.dart b/packages/flutter/lib/src/physics/spring_simulation.dart index a1675239223e..76dfc559a19f 100644 --- a/packages/flutter/lib/src/physics/spring_simulation.dart +++ b/packages/flutter/lib/src/physics/spring_simulation.dart @@ -153,14 +153,11 @@ abstract class _SpringSolution { double initialPosition, double initialVelocity, ) { - final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness; - if (cmk == 0.0) { - return _CriticalSolution(spring, initialPosition, initialVelocity); - } - if (cmk > 0.0) { - return _OverdampedSolution(spring, initialPosition, initialVelocity); - } - return _UnderdampedSolution(spring, initialPosition, initialVelocity); + return switch (spring.damping * spring.damping - 4 * spring.mass * spring.stiffness) { + > 0.0 => _OverdampedSolution(spring, initialPosition, initialVelocity), + < 0.0 => _UnderdampedSolution(spring, initialPosition, initialVelocity), + _ => _CriticalSolution(spring, initialPosition, initialVelocity), + }; } double x(double time); diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 56c67b5cc5a6..ae3b5c64ca20 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -2,7 +2,7 @@ // 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 show SemanticsUpdate; +import 'dart:ui' as ui show PictureRecorder, SceneBuilder, SemanticsUpdate; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -350,6 +350,31 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture return ViewConfiguration.fromView(renderView.flutterView); } + /// Create a [SceneBuilder]. + /// + /// This hook enables test bindings to instrument the rendering layer. + /// + /// This is used by the [RenderView] to create the [SceneBuilder] that is + /// passed to the [Layer] system to render the scene. + ui.SceneBuilder createSceneBuilder() => ui.SceneBuilder(); + + /// Create a [PictureRecorder]. + /// + /// This hook enables test bindings to instrument the rendering layer. + /// + /// This is used by the [PaintingContext] to create the [PictureRecorder]s + /// used when painting [RenderObject]s into [Picture]s passed to + /// [PictureLayer]s. + ui.PictureRecorder createPictureRecorder() => ui.PictureRecorder(); + + /// Create a [Canvas] from a [PictureRecorder]. + /// + /// This hook enables test bindings to instrument the rendering layer. + /// + /// This is used by the [PaintingContext] after creating a [PictureRecorder] + /// using [createPictureRecorder]. + Canvas createCanvas(ui.PictureRecorder recorder) => Canvas(recorder); + /// Called when the system metrics change. /// /// See [dart:ui.PlatformDispatcher.onMetricsChanged]. @@ -638,7 +663,7 @@ String _debugCollectRenderTrees() { /// /// {@template flutter.rendering.debugDumpRenderTree} /// It prints the trees associated with every [RenderView] in -/// [RendererBinding.renderView], separated by two blank lines. +/// [RendererBinding.renderViews], separated by two blank lines. /// {@endtemplate} void debugDumpRenderTree() { debugPrint(_debugCollectRenderTrees()); diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 623eaafb7042..f53b9a5090ab 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -540,14 +540,11 @@ class BoxConstraints extends Constraints { if (affectedFieldsList.length > 1) { affectedFieldsList.add('and ${affectedFieldsList.removeLast()}'); } - String whichFields = ''; - if (affectedFieldsList.length > 2) { - whichFields = affectedFieldsList.join(', '); - } else if (affectedFieldsList.length == 2) { - whichFields = affectedFieldsList.join(' '); - } else { - whichFields = affectedFieldsList.single; - } + final String whichFields = switch (affectedFieldsList.length) { + 1 => affectedFieldsList.single, + 2 => affectedFieldsList.join(' '), + _ => affectedFieldsList.join(', '), + }; throwError(ErrorSummary('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.')); } if (minWidth < 0.0 && minHeight < 0.0) { @@ -984,7 +981,7 @@ extension type const BaselineOffset(double? offset) { /// /// Subclasses do not own their own cache storage. Rather, their [memoize] /// implementation takes a `cacheStorage`. If a prior computation with the same -/// input valus has already been memoized in `cacheStorage`, it returns the +/// input values has already been memoized in `cacheStorage`, it returns the /// memoized value without running `computer`. Otherwise the method runs the /// `computer` to compute the return value, and caches the result to /// `cacheStorage`. @@ -1185,7 +1182,7 @@ final class _LayoutCacheStorage { /// positioned at 0,0. If this is not true, then use [RenderShiftedBox] instead. /// /// See -/// [proxy_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/proxy_box.dart) +/// [proxy_box.dart](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/rendering/proxy_box.dart) /// for examples of inheriting from [RenderProxyBox]. /// /// #### Using RenderShiftedBox @@ -1196,7 +1193,7 @@ final class _LayoutCacheStorage { /// default layout algorithm. /// /// See -/// [shifted_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/shifted_box.dart) +/// [shifted_box.dart](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/rendering/shifted_box.dart) /// for examples of inheriting from [RenderShiftedBox]. /// /// #### Kinds of children and child-specific data @@ -1522,7 +1519,7 @@ abstract class RenderBox extends RenderObject { assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints bool shouldCache = true; assert(() { - // we don't want the checked-mode intrinsic tests to affect + // we don't want the debug-mode intrinsic tests to affect // who gets marked dirty, etc. shouldCache = !RenderObject.debugCheckingIntrinsics; return true; @@ -2046,7 +2043,7 @@ abstract class RenderBox extends RenderObject { final double? baselineOffset = _computeIntrinsics(_CachedLayoutCalculation.baseline, (constraints, baseline), _computeDryBaseline).offset; // This assert makes sure computeDryBaseline always gets called in debug mode, // in case the computeDryBaseline implementation invokes debugCannotComputeDryLayout. - // This check should be skipped when debugCheckintIntrinsics is true to avoid + // This check should be skipped when debugCheckingIntrinsics is true to avoid // slowing down the app significantly. assert(RenderObject.debugCheckingIntrinsics || baselineOffset == computeDryBaseline(constraints, baseline)); return baselineOffset; @@ -2194,7 +2191,7 @@ abstract class RenderBox extends RenderObject { '${objectRuntimeType(renderBoxDoingDryBaseline, 'RenderBox')}.computeDryBaseline.' 'The computeDryBaseline method must not access ' '${renderBoxDoingDryBaseline == this ? "the RenderBox's own size" : "the size of its child"},' - "because it's established in peformLayout or peformResize using different BoxConstraints." + "because it's established in performLayout or performResize using different BoxConstraints." ); assert(size == _size); } diff --git a/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart index cfb0812194ed..7daca9893ac1 100644 --- a/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart +++ b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart @@ -129,15 +129,11 @@ mixin DebugOverflowIndicatorMixin on RenderObject { String _formatPixels(double value) { assert(value > 0.0); - final String pixels; - if (value > 10.0) { - pixels = value.toStringAsFixed(0); - } else if (value > 1.0) { - pixels = value.toStringAsFixed(1); - } else { - pixels = value.toStringAsPrecision(3); - } - return pixels; + return switch (value) { + > 10.0 => value.toStringAsFixed(0), + > 1.0 => value.toStringAsFixed(1), + _ => value.toStringAsPrecision(3), + }; } List<_OverflowRegionData> _calculateOverflowRegions(RelativeRect overflow, Rect containerRect) { diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index ba386545a6a8..e03b1d9995a0 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -2312,18 +2312,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ..layout(minWidth: minWidth, maxWidth: maxWidth); positionInlineChildren(_textPainter.inlinePlaceholderBoxes!); _computeCaretPrototype(); - // We grab _textPainter.size here because assigning to `size` on the next - // line will trigger us to validate our intrinsic sizes, which will change - // _textPainter's layout because the intrinsic size calculations are - // destructive, which would mean we would get different results if we later - // used properties on _textPainter in this method. - // Other _textPainter state like didExceedMaxLines will also be affected, - // though we currently don't use those here. - // See also RenderParagraph which has a similar issue. - final Size textPainterSize = _textPainter.size; + final double width = forceLine ? constraints.maxWidth - : constraints.constrainWidth(_textPainter.size.width + _caretMargin); + : constraints.constrainWidth(_textPainter.width + _caretMargin); assert(maxLines != 1 || _textPainter.maxLines == 1); final double preferredHeight = switch (maxLines) { null => math.max(_textPainter.height, preferredLineHeight * (minLines ?? 0)), @@ -2336,7 +2328,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, }; size = Size(width, constraints.constrainHeight(preferredHeight)); - final Size contentSize = Size(textPainterSize.width + _caretMargin, textPainterSize.height); + final Size contentSize = Size(_textPainter.width + _caretMargin, _textPainter.height); final BoxConstraints painterConstraints = BoxConstraints.tight(contentSize); diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index 61277ddb4bc1..13625b673a3e 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -45,7 +45,7 @@ extension type const _AxisSize._(Size _size) { // The ascent and descent of a baseline-aligned child. // // Baseline-aligned children contributes to the cross axis extent of a [RenderFlex] -// differently from chidren with other [CrossAxisAlignment]s. +// differently from children with other [CrossAxisAlignment]s. extension type const _AscentDescent._((double ascent, double descent)? ascentDescent) { factory _AscentDescent({ required double? baselineOffset, required double crossSize }) { return baselineOffset == null ? none : _AscentDescent._((baselineOffset, crossSize - baselineOffset)); @@ -76,7 +76,7 @@ class _LayoutSizes { final _AxisSize axisSize; // The free space along the main axis. If the value is positive, the free space - // will be distributed according to the [MainAxisAliggnment] specified. A + // will be distributed according to the [MainAxisAlignment] specified. A // negative value indicates the RenderFlex overflows along the main axis. final double mainAxisFreeSpace; @@ -1060,7 +1060,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin { /// Create a new layer handle, optionally referencing a [Layer]. LayerHandle([this._layer]) { diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index c1b625ff034c..6f2fee63b0d1 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -11,6 +11,7 @@ import 'package:flutter/painting.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/semantics.dart'; +import 'binding.dart'; import 'debug.dart'; import 'layer.dart'; @@ -331,8 +332,8 @@ class PaintingContext extends ClipContext { void _startRecording() { assert(!_isRecording); _currentLayer = PictureLayer(estimatedBounds); - _recorder = ui.PictureRecorder(); - _canvas = Canvas(_recorder!); + _recorder = RendererBinding.instance.createPictureRecorder(); + _canvas = RendererBinding.instance.createCanvas(_recorder!); _containerLayer.append(_currentLayer!); } @@ -2205,11 +2206,11 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge } Constraints? _constraints; - /// Verify that the object's constraints are being met. Override - /// this function in a subclass to verify that your state matches - /// the constraints object. This function is only called in checked - /// mode and only when needsLayout is false. If the constraints are - /// not met, it should assert or throw an exception. + /// Verify that the object's constraints are being met. Override this function + /// in a subclass to verify that your state matches the constraints object. + /// This function is only called when asserts are enabled (i.e. in debug mode) + /// and only when needsLayout is false. If the constraints are not met, it + /// should assert or throw an exception. @protected void debugAssertDoesMeetConstraints(); diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 16233a28b229..13f3338bb82b 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -809,16 +809,10 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin[DiagnosticsNode.message('table is empty')]; } - final List children = []; - for (int y = 0; y < rows; y += 1) { - for (int x = 0; x < columns; x += 1) { - final int xy = x + y * columns; - final RenderBox? child = _children[xy]; - final String name = 'child ($x, $y)'; - if (child != null) { - children.add(child.toDiagnosticsNode(name: name)); - } else { - children.add(DiagnosticsProperty(name, null, ifNull: 'is null', showSeparator: false)); - } - } - } - return children; + return [ + for (int y = 0; y < rows; y += 1) + for (int x = 0; x < columns; x += 1) + if (_children[x + y * columns] case final RenderBox child) + child.toDiagnosticsNode(name: 'child ($x, $y)') + else + DiagnosticsProperty('child ($x, $y)', null, ifNull: 'is null', showSeparator: false), + ]; } } diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index c4842cfc00af..66317a23257a 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -66,6 +66,19 @@ class ViewConfiguration { return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); } + /// Returns whether [toMatrix] would return a different value for this + /// configuration than it would for the given `oldConfiguration`. + bool shouldUpdateMatrix(ViewConfiguration oldConfiguration) { + if (oldConfiguration.runtimeType != runtimeType) { + // New configuration could have different logic, so we don't know + // whether it will need a new transform. Return a conservative result. + return true; + } + // For this class, the only input to toMatrix is the device pixel ratio, + // so we return true if they differ and false otherwise. + return oldConfiguration.devicePixelRatio != devicePixelRatio; + } + /// Transforms the provided [Size] in logical pixels to physical pixels. /// /// The [FlutterView.render] method accepts only sizes in physical pixels, but @@ -103,6 +116,16 @@ class ViewConfiguration { /// The view represents the total output surface of the render tree and handles /// bootstrapping the rendering pipeline. The view has a unique child /// [RenderBox], which is required to fill the entire output surface. +/// +/// This object must be bootstrapped in a specific order: +/// +/// 1. First, set the [configuration] (either in the constructor or after +/// construction). +/// 2. Second, [attach] the object to a [PipelineOwner]. +/// 3. Third, use [prepareInitialFrame] to bootstrap the layout and paint logic. +/// +/// After the bootstrapping is complete, the [compositeFrame] method may be used +/// to obtain the rendered output. class RenderView extends RenderObject with RenderObjectWithChildMixin { /// Creates the root of the render tree. /// @@ -140,6 +163,9 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin /// [TestFlutterView.physicalSize] on the appropriate [TestFlutterView] /// (typically [WidgetTester.view]) instead of setting a configuration /// directly on the [RenderView]. + /// + /// A [configuration] must be set (either directly or by passing one to the + /// constructor) before calling [prepareInitialFrame]. ViewConfiguration get configuration => _configuration!; ViewConfiguration? _configuration; set configuration(ViewConfiguration value) { @@ -149,10 +175,10 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin final ViewConfiguration? oldConfiguration = _configuration; _configuration = value; if (_rootTransform == null) { - // [prepareInitialFrame] has not been called yet, nothing to do for now. + // [prepareInitialFrame] has not been called yet, nothing more to do for now. return; } - if (oldConfiguration?.toMatrix() != configuration.toMatrix()) { + if (oldConfiguration == null || configuration.shouldUpdateMatrix(oldConfiguration)) { replaceRootLayer(_updateMatricesAndCreateNewRootLayer()); } assert(_rootTransform != null); @@ -160,6 +186,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin } /// Whether a [configuration] has been set. + /// + /// This must be true before calling [prepareInitialFrame]. bool get hasConfiguration => _configuration != null; @override @@ -202,15 +230,23 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin /// Bootstrap the rendering pipeline by preparing the first frame. /// - /// This should only be called once, and must be called before changing - /// [configuration]. It is typically called immediately after calling the - /// constructor. + /// This should only be called once. It is typically called immediately after + /// setting the [configuration] the first time (whether by passing one to the + /// constructor, or setting it directly). The [configuration] must have been + /// set before calling this method, and the [RenderView] must have been + /// attached to a [PipelineOwner] using [attach]. /// /// This does not actually schedule the first frame. Call - /// [PipelineOwner.requestVisualUpdate] on [owner] to do that. + /// [PipelineOwner.requestVisualUpdate] on the [owner] to do that. + /// + /// This should be called before using any methods that rely on the [layer] + /// being initialized, such as [compositeFrame]. + /// + /// This method calls [scheduleInitialLayout] and [scheduleInitialPaint]. void prepareInitialFrame() { - assert(owner != null); - assert(_rootTransform == null); + assert(owner != null, 'attach the RenderView to a PipelineOwner before calling prepareInitialFrame'); + assert(_rootTransform == null, 'prepareInitialFrame must only be called once'); // set by _updateMatricesAndCreateNewRootLayer + assert(hasConfiguration, 'set a configuration before calling prepareInitialFrame'); scheduleInitialLayout(); scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); assert(_rootTransform != null); @@ -219,6 +255,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin Matrix4? _rootTransform; TransformLayer _updateMatricesAndCreateNewRootLayer() { + assert(hasConfiguration); _rootTransform = configuration.toMatrix(); final TransformLayer rootLayer = TransformLayer(transform: _rootTransform); rootLayer.attach(this); @@ -295,12 +332,19 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin /// Uploads the composited layer tree to the engine. /// /// Actually causes the output of the rendering pipeline to appear on screen. + /// + /// Before calling this method, the [owner] must be set by calling [attach], + /// the [configuration] must be set to a non-null value, and the + /// [prepareInitialFrame] method must have been called. void compositeFrame() { if (!kReleaseMode) { FlutterTimeline.startSync('COMPOSITING'); } try { - final ui.SceneBuilder builder = ui.SceneBuilder(); + assert(hasConfiguration, 'set the RenderView configuration before calling compositeFrame'); + assert(_rootTransform != null, 'call prepareInitialFrame before calling compositeFrame'); + assert(layer != null, 'call prepareInitialFrame before calling compositeFrame'); + final ui.SceneBuilder builder = RendererBinding.instance.createSceneBuilder(); final ui.Scene scene = layer!.buildScene(builder); if (automaticSystemUiAdjustment) { _updateSystemChrome(); diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index e6105ed6537f..14825107a942 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -205,20 +205,15 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { // This is run in another isolate created by _addLicenses above. static List _parseLicenses(String rawLicenses) { final String licenseSeparator = '\n${'-' * 80}\n'; - final List result = []; - final List licenses = rawLicenses.split(licenseSeparator); - for (final String license in licenses) { - final int split = license.indexOf('\n\n'); - if (split >= 0) { - result.add(LicenseEntryWithLineBreaks( - license.substring(0, split).split('\n'), - license.substring(split + 2), - )); - } else { - result.add(LicenseEntryWithLineBreaks(const [], license)); - } - } - return result; + return [ + for (final String license in rawLicenses.split(licenseSeparator)) + if (license.indexOf('\n\n') case final int split when split >= 0) + LicenseEntryWithLineBreaks( + license.substring(0, split).split('\n'), + license.substring(split + 2), + ) + else LicenseEntryWithLineBreaks(const [], license), + ]; } @override diff --git a/packages/flutter/lib/src/services/hardware_keyboard.dart b/packages/flutter/lib/src/services/hardware_keyboard.dart index 4fea3a7a3670..390c0fe8bb91 100644 --- a/packages/flutter/lib/src/services/hardware_keyboard.dart +++ b/packages/flutter/lib/src/services/hardware_keyboard.dart @@ -625,14 +625,13 @@ class HardwareKeyboard { } List _debugPressedKeysDetails() { - if (_pressedKeys.isEmpty) { - return ['Empty']; - } - final List details = []; - for (final PhysicalKeyboardKey physicalKey in _pressedKeys.keys) { - details.add('$physicalKey: ${_pressedKeys[physicalKey]}'); - } - return details; + return [ + if (_pressedKeys.isEmpty) + 'Empty' + else + for (final PhysicalKeyboardKey physicalKey in _pressedKeys.keys) + '$physicalKey: ${_pressedKeys[physicalKey]}', + ]; } /// Process a new [KeyEvent] by recording the state changes and dispatching diff --git a/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart b/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart index a257d6f86495..ae4aa0ddf3dd 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_fuchsia.dart @@ -53,7 +53,7 @@ class RawKeyEventDataFuchsia extends RawKeyEventData { /// The modifiers that were present when the key event occurred. /// - /// See + /// See /// for the numerical values of the modifiers. Many of these are also /// replicated as static constants in this class. /// diff --git a/packages/flutter/lib/src/services/raw_keyboard_windows.dart b/packages/flutter/lib/src/services/raw_keyboard_windows.dart index f9f6169f11fd..52f9edaf08fb 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_windows.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_windows.dart @@ -230,7 +230,7 @@ class RawKeyEventDataWindows extends RawKeyEventData { // These are not the values defined by the Windows header for each modifier. Since they // can't be packaged into a single int, we are re-defining them here to reduce the size // of the message from the embedder. Embedders should map these values to the native key codes. - // Keep this in sync with https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc + // Keep this in sync with https://github.com/flutter/engine/blob/main/shell/platform/windows/key_event_handler.cc /// This mask is used to check the [modifiers] field to test whether one of the /// SHIFT modifier keys is pressed. diff --git a/packages/flutter/lib/src/services/spell_check.dart b/packages/flutter/lib/src/services/spell_check.dart index 24e36c3754b1..c07e63cd431f 100644 --- a/packages/flutter/lib/src/services/spell_check.dart +++ b/packages/flutter/lib/src/services/spell_check.dart @@ -180,20 +180,13 @@ class DefaultSpellCheckService implements SpellCheckService { return null; } - List suggestionSpans = []; - - for (final dynamic result in rawResults) { - final Map resultMap = - Map.from(result as Map); - suggestionSpans.add( + List suggestionSpans = [ + for (final Map resultMap in rawResults.cast>()) SuggestionSpan( - TextRange( - start: resultMap['startIndex'] as int, - end: resultMap['endIndex'] as int), - (resultMap['suggestions'] as List).cast(), - ) - ); - } + TextRange(start: resultMap['startIndex'] as int, end: resultMap['endIndex'] as int), + (resultMap['suggestions'] as List).cast(), + ), + ]; if (lastSavedResults != null) { // Merge current and previous spell check results if between requests, diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index d133d6857a3d..f46a753e30c8 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1894,14 +1894,11 @@ class TextInput { TextInput._instance._updateEditingValue(value, exclude: _PlatformTextInputControl.instance); case 'TextInputClient.updateEditingStateWithDeltas': assert(_currentConnection!._client is DeltaTextInputClient, 'You must be using a DeltaTextInputClient if TextInputConfiguration.enableDeltaModel is set to true'); - final List deltas = []; - final Map encoded = args[1] as Map; - - for (final dynamic encodedDelta in encoded['deltas'] as List) { - final TextEditingDelta delta = TextEditingDelta.fromJSON(encodedDelta as Map); - deltas.add(delta); - } + final List deltas = [ + for (final dynamic encodedDelta in encoded['deltas'] as List) + TextEditingDelta.fromJSON(encodedDelta as Map) + ]; (_currentConnection!._client as DeltaTextInputClient).updateEditingValueWithDeltas(deltas); case 'TextInputClient.performAction': diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 92b450ae5221..0e8dd6833189 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -6686,16 +6686,11 @@ class MouseRegion extends SingleChildRenderObjectWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final List listeners = []; - if (onEnter != null) { - listeners.add('enter'); - } - if (onExit != null) { - listeners.add('exit'); - } - if (onHover != null) { - listeners.add('hover'); - } + final List listeners = [ + if (onEnter != null) 'enter', + if (onExit != null) 'exit', + if (onHover != null) 'hover', + ]; properties.add(IterableProperty('listeners', listeners, ifEmpty: '')); properties.add(DiagnosticsProperty('cursor', cursor, defaultValue: null)); properties.add(DiagnosticsProperty('opaque', opaque, defaultValue: true)); @@ -7573,12 +7568,10 @@ class KeyedSubtree extends StatelessWidget { return items; } - final List itemsWithUniqueKeys = []; - int itemIndex = baseIndex; - for (final Widget item in items) { - itemsWithUniqueKeys.add(KeyedSubtree.wrap(item, itemIndex)); - itemIndex += 1; - } + final List itemsWithUniqueKeys = [ + for (final (int i, Widget item) in items.indexed) + KeyedSubtree.wrap(item, baseIndex + i), + ]; assert(!debugItemsHaveDuplicateKeys(itemsWithUniqueKeys)); return itemsWithUniqueKeys; diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index c68b8665c973..8cc341582eb6 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -414,6 +414,8 @@ class LongPressDraggable extends Draggable { super.ignoringFeedbackPointer, this.delay = kLongPressTimeout, super.allowedButtonsFilter, + super.hitTestBehavior, + super.rootOverlay, }); /// Whether haptic feedback should be triggered on drag start. @@ -909,17 +911,12 @@ class _DragAvatar extends Drag { Iterable<_DragTargetState> _getDragTargets(Iterable path) { // Look for the RenderBoxes that corresponds to the hit target (the hit target // widgets build RenderMetaData boxes for us for this purpose). - final List<_DragTargetState> targets = <_DragTargetState>[]; - for (final HitTestEntry entry in path) { - final HitTestTarget target = entry.target; - if (target is RenderMetaData) { - final dynamic metaData = target.metaData; - if (metaData is _DragTargetState && metaData.isExpectedDataType(data, T)) { - targets.add(metaData); - } - } - } - return targets; + return <_DragTargetState>[ + for (final HitTestEntry entry in path) + if (entry.target case final RenderMetaData target) + if (target.metaData case final _DragTargetState metaData) + if (metaData.isExpectedDataType(data, T)) metaData, + ]; } void _leaveAllEntered() { @@ -971,12 +968,10 @@ class _DragAvatar extends Drag { } Offset _restrictAxis(Offset offset) { - if (axis == null) { - return offset; - } - if (axis == Axis.horizontal) { - return Offset(offset.dx, 0.0); - } - return Offset(0.0, offset.dy); + return switch (axis) { + Axis.horizontal => Offset(offset.dx, 0.0), + Axis.vertical => Offset(0.0, offset.dy), + null => offset, + }; } } diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 19e6294a5aeb..24ac44e5d3b7 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -2035,15 +2035,11 @@ class _HighlightModeManager { // Check to see if any of the early handlers handle the key. If so, then // return early. if (_earlyKeyEventHandlers.isNotEmpty) { - final List results = []; - // Copy the list before iteration to prevent problems if the list gets - // modified during iteration. - final List iterationList = _earlyKeyEventHandlers.toList(); - for (final OnKeyEventCallback callback in iterationList) { - for (final KeyEvent event in message.events) { - results.add(callback(event)); - } - } + final List results = [ + // Make a copy to prevent problems if the list is modified during iteration. + for (final OnKeyEventCallback callback in _earlyKeyEventHandlers.toList()) + for (final KeyEvent event in message.events) callback(event), + ]; final KeyEventResult result = combineKeyEventResults(results); switch (result) { case KeyEventResult.ignored: @@ -2067,15 +2063,13 @@ class _HighlightModeManager { FocusManager.instance.primaryFocus!, ...FocusManager.instance.primaryFocus!.ancestors, ]) { - final List results = []; - if (node.onKeyEvent != null) { - for (final KeyEvent event in message.events) { - results.add(node.onKeyEvent!(node, event)); - } - } - if (node.onKey != null && message.rawEvent != null) { - results.add(node.onKey!(node, message.rawEvent!)); - } + final List results = [ + if (node.onKeyEvent != null) + for (final KeyEvent event in message.events) + node.onKeyEvent!(node, event), + if (node.onKey != null && message.rawEvent != null) + node.onKey!(node, message.rawEvent!), + ]; final KeyEventResult result = combineKeyEventResults(results); switch (result) { case KeyEventResult.ignored: @@ -2095,15 +2089,11 @@ class _HighlightModeManager { // Check to see if any late key event handlers want to handle the event. if (!handled && _lateKeyEventHandlers.isNotEmpty) { - final List results = []; - // Copy the list before iteration to prevent problems if the list gets - // modified during iteration. - final List iterationList = _lateKeyEventHandlers.toList(); - for (final OnKeyEventCallback callback in iterationList) { - for (final KeyEvent event in message.events) { - results.add(callback(event)); - } - } + final List results = [ + // Make a copy to prevent problems if the list is modified during iteration. + for (final OnKeyEventCallback callback in _lateKeyEventHandlers.toList()) + for (final KeyEvent event in message.events) callback(event), + ]; final KeyEventResult result = combineKeyEventResults(results); switch (result) { case KeyEventResult.ignored: diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 2211523a4a65..f17529b8b336 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -8,6 +8,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'basic.dart'; +import 'focus_manager.dart'; +import 'focus_scope.dart'; import 'framework.dart'; import 'navigator.dart'; import 'pop_scope.dart'; @@ -53,16 +55,22 @@ class Form extends StatefulWidget { super.key, required this.child, this.canPop, + @Deprecated( + 'Use onPopInvokedWithResult instead. ' + 'This feature was deprecated after v3.22.0-12.0.pre.', + ) this.onPopInvoked, + this.onPopInvokedWithResult, @Deprecated( - 'Use canPop and/or onPopInvoked instead. ' + 'Use canPop and/or onPopInvokedWithResult instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) this.onWillPop, this.onChanged, AutovalidateMode? autovalidateMode, }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled, - assert((onPopInvoked == null && canPop == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvoked.'); + assert(onPopInvokedWithResult == null || onPopInvoked == null, 'onPopInvoked is deprecated; use onPopInvokedWithResult'), + assert(((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.'); /// Returns the [FormState] of the closest [Form] widget which encloses the /// given context, or null if none is found. @@ -142,7 +150,7 @@ class Form extends StatefulWidget { /// * [WillPopScope], another widget that provides a way to intercept the /// back button. @Deprecated( - 'Use canPop and/or onPopInvoked instead. ' + 'Use canPop and/or onPopInvokedWithResult instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) final WillPopCallback? onWillPop; @@ -158,11 +166,18 @@ class Form extends StatefulWidget { /// /// See also: /// - /// * [onPopInvoked], which also comes from [PopScope] and is often used in + /// * [onPopInvokedWithResult], which also comes from [PopScope] and is often used in /// conjunction with this parameter. /// * [PopScope.canPop], which is what [Form] delegates to internally. final bool? canPop; + /// {@macro flutter.widgets.navigator.onPopInvoked} + @Deprecated( + 'Use onPopInvokedWithResult instead. ' + 'This feature was deprecated after v3.22.0-12.0.pre.', + ) + final PopInvokedCallback? onPopInvoked; + /// {@macro flutter.widgets.navigator.onPopInvoked} /// /// {@tool dartpad} @@ -176,8 +191,8 @@ class Form extends StatefulWidget { /// /// * [canPop], which also comes from [PopScope] and is often used in /// conjunction with this parameter. - /// * [PopScope.onPopInvoked], which is what [Form] delegates to internally. - final PopInvokedCallback? onPopInvoked; + /// * [PopScope.onPopInvokedWithResult], which is what [Form] delegates to internally. + final PopInvokedWithResultCallback? onPopInvokedWithResult; /// Called when one of the form fields changes. /// @@ -191,6 +206,14 @@ class Form extends StatefulWidget { /// {@macro flutter.widgets.FormField.autovalidateMode} final AutovalidateMode autovalidateMode; + void _callPopInvoked(bool didPop, Object? result) { + if (onPopInvokedWithResult != null) { + onPopInvokedWithResult!(didPop, result); + return; + } + onPopInvoked?.call(didPop); + } + @override FormState createState() => FormState(); } @@ -224,10 +247,22 @@ class FormState extends State
{ void _register(FormFieldState field) { _fields.add(field); + if (widget.autovalidateMode == AutovalidateMode.onUnfocus) { + field._focusNode.addListener(() => _updateField(field)); + } } void _unregister(FormFieldState field) { _fields.remove(field); + if (widget.autovalidateMode == AutovalidateMode.onUnfocus) { + field._focusNode.removeListener(()=> _updateField(field)); + } + } + + void _updateField(FormFieldState field) { + if (!field._focusNode.hasFocus) { + _validate(); + } } @override @@ -239,14 +274,15 @@ class FormState extends State { if (_hasInteractedByUser) { _validate(); } + case AutovalidateMode.onUnfocus: case AutovalidateMode.disabled: break; } - if (widget.canPop != null || widget.onPopInvoked != null) { - return PopScope( + if (widget.canPop != null || (widget.onPopInvokedWithResult ?? widget.onPopInvoked) != null) { + return PopScope( canPop: widget.canPop ?? true, - onPopInvoked: widget.onPopInvoked, + onPopInvokedWithResult: widget._callPopInvoked, child: _FormScope( formState: this, generation: _generation, @@ -323,12 +359,16 @@ class FormState extends State { bool _validate([Set>? invalidFields]) { bool hasError = false; String errorMessage = ''; + final bool validateOnFocusChange = widget.autovalidateMode == AutovalidateMode.onUnfocus; + for (final FormFieldState field in _fields) { - final bool isFieldValid = field.validate(); - hasError = !isFieldValid || hasError; - errorMessage += field.errorText ?? ''; - if (invalidFields != null && !isFieldValid) { - invalidFields.add(field); + if (!validateOnFocusChange || !field._focusNode.hasFocus) { + final bool isFieldValid = field.validate(); + hasError = !isFieldValid || hasError; + errorMessage += field.errorText ?? ''; + if (invalidFields != null && !isFieldValid) { + invalidFields.add(field); + } } } @@ -343,6 +383,7 @@ class FormState extends State { SemanticsService.announce(errorMessage, directionality, assertiveness: Assertiveness.assertive); } } + return !hasError; } } @@ -494,6 +535,7 @@ class FormFieldState extends State> with RestorationMixin { late T? _value = widget.initialValue; final RestorableStringN _errorText = RestorableStringN(null); final RestorableBool _hasInteractedByUser = RestorableBool(false); + final FocusNode _focusNode = FocusNode(); /// The current value of the form field. T? get value => _value; @@ -604,6 +646,7 @@ class FormFieldState extends State> with RestorationMixin { @override void dispose() { _errorText.dispose(); + _focusNode.dispose(); _hasInteractedByUser.dispose(); super.dispose(); } @@ -618,13 +661,34 @@ class FormFieldState extends State> with RestorationMixin { if (_hasInteractedByUser.value) { _validate(); } + case AutovalidateMode.onUnfocus: case AutovalidateMode.disabled: break; } } + Form.maybeOf(context)?._register(this); + + if (Form.maybeOf(context)?.widget.autovalidateMode == AutovalidateMode.onUnfocus && widget.autovalidateMode != AutovalidateMode.always || + widget.autovalidateMode == AutovalidateMode.onUnfocus) { + return Focus( + canRequestFocus: false, + skipTraversal: true, + onFocusChange: (bool value) { + if (!value) { + setState(() { + _validate(); + }); + } + }, + focusNode: _focusNode, + child: widget.builder(this), + ); + } + return widget.builder(this); } + } /// Used to configure the auto validation of [FormField] and [Form] widgets. @@ -638,4 +702,11 @@ enum AutovalidateMode { /// Used to auto-validate [Form] and [FormField] only after each user /// interaction. onUserInteraction, + + /// Used to auto-validate [Form] and [FormField] only after the field has + /// lost focus. + /// + /// In order to validate all fields of a [Form] after the first time the user interacts + /// with one, use [always] instead. + onUnfocus, } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index f6fd1a087f4d..f45862549b4b 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -3194,14 +3194,11 @@ class BuildOwner { keyStringCount[key] = 1; } } - final List keyLabels = []; - keyStringCount.forEach((String key, int count) { - if (count == 1) { - keyLabels.add(key); - } else { - keyLabels.add('$key ($count different affected keys had this toString representation)'); - } - }); + final List keyLabels = [ + for (final MapEntry(:String key, value: int count) in keyStringCount.entries) + if (count == 1) key + else '$key ($count different affected keys had this toString representation)', + ]; final Iterable elements = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans!.keys; final Map elementStringCount = HashMap(); for (final String element in elements.map((Element element) => element.toString())) { @@ -3211,14 +3208,11 @@ class BuildOwner { elementStringCount[element] = 1; } } - final List elementLabels = []; - elementStringCount.forEach((String element, int count) { - if (count == 1) { - elementLabels.add(element); - } else { - elementLabels.add('$element ($count different affected elements had this toString representation)'); - } - }); + final List elementLabels = [ + for (final MapEntry(key: String element, value: int count) in elementStringCount.entries) + if (count == 1) element + else '$element ($count different affected elements had this toString representation)', + ]; assert(keyLabels.isNotEmpty); final String the = keys.length == 1 ? ' the' : ''; final String s = keys.length == 1 ? '' : 's'; diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index 4ca022cb7b12..8764da8d6b9f 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -425,7 +425,6 @@ class _HeroState extends State { } // Everything known about a hero flight that's to be started or diverted. -@immutable class _HeroFlightManifest { _HeroFlightManifest({ required this.type, @@ -455,8 +454,10 @@ class _HeroFlightManifest { Object get tag => fromHero.widget.tag; + CurvedAnimation? _animation; + Animation get animation { - return CurvedAnimation( + return _animation ??= CurvedAnimation( parent: (type == HeroFlightDirection.push) ? toRoute.animation! : fromRoute.animation!, curve: Curves.fastOutSlowIn, reverseCurve: isDiverted ? null : Curves.fastOutSlowIn.flipped, @@ -505,6 +506,11 @@ class _HeroFlightManifest { return '_HeroFlightManifest($type tag: $tag from route: ${fromRoute.settings} ' 'to route: ${toRoute.settings} with hero: $fromHero to $toHero)${isValid ? '' : ', INVALID'}'; } + + @mustCallSuper + void dispose() { + _animation?.dispose(); + } } // Builds the in-flight hero widget. @@ -531,7 +537,13 @@ class _HeroFlight { late ProxyAnimation _proxyAnimation; // The manifest will be available once `start` is called, throughout the // flight's lifecycle. - late _HeroFlightManifest manifest; + _HeroFlightManifest? _manifest; + _HeroFlightManifest get manifest => _manifest!; + set manifest (_HeroFlightManifest value) { + _manifest?.dispose(); + _manifest = value; + } + OverlayEntry? overlayEntry; bool _aborted = false; @@ -634,6 +646,7 @@ class _HeroFlight { _proxyAnimation.removeListener(onTick); _proxyAnimation.removeStatusListener(_handleAnimationUpdate); } + _manifest?.dispose(); } void onTick() { diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 88b869baf593..bed9a85ac15d 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -355,7 +355,7 @@ abstract class Route extends _RoutePlaceholder { /// will still be called. The `didPop` parameter indicates whether or not the /// back navigation actually happened successfully. /// {@endtemplate} - void onPopInvoked(bool didPop) {} + void onPopInvoked(bool didPop, T? result) {} /// Whether calling [didPop] would return false. bool get willHandlePopInternally => false; @@ -703,7 +703,7 @@ class NavigatorObserver { /// The [Navigator]'s routes are being moved by a user gesture. /// /// For example, this is called when an iOS back gesture starts, and is used - /// to disabled hero animations during such interactions. + /// to disable hero animations during such interactions. void didStartUserGesture(Route route, Route? previousRoute) { } /// User gesture is no longer controlling the [Navigator]. @@ -1573,7 +1573,7 @@ class Navigator extends StatefulWidget { /// * [Navigator], which explains under the heading "state restoration" /// how and under what conditions the navigator restores its state. /// * [Navigator.restorablePush], which includes an example showcasing how - /// to push a restorable route unto the navigator. + /// to push a restorable route onto the navigator. /// {@endtemplate} final String? restorationScopeId; @@ -1609,7 +1609,7 @@ class Navigator extends StatefulWidget { /// The callback must return a list of [Route] objects with which the history /// will be primed. /// - /// When parsing the initialRoute, if there's any chance that the it may + /// When parsing the initialRoute, if there's any chance that it may /// contain complex characters, it's best to use the /// [characters](https://pub.dev/packages/characters) API. This will ensure /// that extended grapheme clusters and surrogate pairs are treated as single @@ -1837,7 +1837,7 @@ class Navigator extends StatefulWidget { /// [NavigatorObserver.didReplace]). The removed route is notified once the /// new route has finished animating (see [Route.didComplete]). The removed /// route's exit animation is not run (see [popAndPushNamed] for a variant - /// that does animated the removed route). + /// that animates the removed route). /// /// Ongoing gestures within the current route are canceled when a new route is /// pushed. @@ -2667,7 +2667,7 @@ class Navigator extends StatefulWidget { /// this class is given instead. Useful for pushing contents above all /// subsequent instances of [Navigator]. /// - /// If there is no [Navigator] in the give `context`, this function will throw + /// If there is no [Navigator] in the given `context`, this function will throw /// a [FlutterError] in debug mode, and an exception in release mode. /// /// This method can be expensive (it walks the element tree). @@ -2801,7 +2801,7 @@ class Navigator extends StatefulWidget { // Null route might be a result of gap in initialRouteName // // For example, routes = ['A', 'A/B/C'], and initialRouteName = 'A/B/C' - // This should result in result = ['A', null,'A/B/C'] where 'A/B' produces + // This should result in result = ['A', null, 'A/B/C'] where 'A/B' produces // the null. In this case, we want to filter out the null and return // result = ['A', 'A/B/C']. result.removeWhere((Route? route) => route == null); @@ -3109,7 +3109,7 @@ class _RouteEntry extends RouteTransitionRecord { assert(isPresent); pendingResult = result; currentState = _RouteLifecycle.pop; - route.onPopInvoked(true); + route.onPopInvoked(true, result); } bool _reportRemovalToObserver = true; @@ -5239,7 +5239,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res pop(result); return true; case RoutePopDisposition.doNotPop: - lastEntry.route.onPopInvoked(false); + lastEntry.route.onPopInvoked(false, result); return true; } } @@ -5282,7 +5282,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res assert(entry.route._popCompleter.isCompleted); entry.currentState = _RouteLifecycle.pop; } - entry.route.onPopInvoked(true); + entry.route.onPopInvoked(true, result); } else { entry.pop(result); assert (entry.currentState == _RouteLifecycle.pop); diff --git a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart index 203a85beded1..ea2be6695551 100644 --- a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart +++ b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart @@ -81,9 +81,9 @@ class _NavigatorPopHandlerState extends State { Widget build(BuildContext context) { // When the widget subtree indicates it can handle a pop, disable popping // here, so that it can be manually handled in canPop. - return PopScope( + return PopScope( canPop: !widget.enabled || _canPop, - onPopInvoked: (bool didPop) { + onPopInvokedWithResult: (bool didPop, Object? result) { if (didPop) { return; } diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index 6deacd25d008..21c27720a73e 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -1929,7 +1929,7 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation> // // Generally, `assert(_debugIsLocationValid())` should be used to prevent // invalid accesses to an invalid `_OverlayEntryLocation` object. Exceptions - // to this rule are _removeChild, _deactive, which will be called when the + // to this rule are _removeChild, _deactivate, which will be called when the // OverlayPortal is being removed from the widget tree and may use the // location information to perform cleanup tasks. // diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart index ab925cd20f47..00b0767af505 100644 --- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart +++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart @@ -296,7 +296,7 @@ class _GlowingOverscrollIndicatorState extends State } // The Glow logic is a port of the logic in the following file: -// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/EdgeEffect.java +// https://android.googlesource.com/platform/frameworks/base/+/main/core/java/android/widget/EdgeEffect.java // as of December 2016. enum _GlowState { idle, absorb, pull, recede } @@ -841,15 +841,23 @@ class _StretchController extends ChangeNotifier { double get value => _stretchSize.value; + // Constants for absorbImpact. + static const double _kMinVelocity = 1; + static const double _kMaxVelocity = 10000; + static const Duration _kMinStretchDuration = Duration(milliseconds: 50); + /// Handle a fling to the edge of the viewport at a particular velocity. /// /// The velocity must be positive. void absorbImpact(double velocity, double totalOverscroll) { assert(velocity >= 0.0); - velocity = clampDouble(velocity, 1, 10000); + velocity = clampDouble(velocity, _kMinVelocity, _kMaxVelocity); _stretchSizeTween.begin = _stretchSize.value; _stretchSizeTween.end = math.min(_stretchIntensity + (_flingFriction / velocity), 1.0); - _stretchController.duration = Duration(milliseconds: (velocity * 0.02).round()); + _stretchController.duration = Duration( + milliseconds: + math.max(velocity * 0.02, _kMinStretchDuration.inMilliseconds).round(), + ); _stretchController.forward(from: 0.0); _state = _StretchState.absorb; _stretchDirection = totalOverscroll > 0 ? _StretchDirection.trailing : _StretchDirection.leading; diff --git a/packages/flutter/lib/src/widgets/platform_menu_bar.dart b/packages/flutter/lib/src/widgets/platform_menu_bar.dart index 687894d37805..4f743816476d 100644 --- a/packages/flutter/lib/src/widgets/platform_menu_bar.dart +++ b/packages/flutter/lib/src/widgets/platform_menu_bar.dart @@ -666,22 +666,12 @@ class PlatformMenuItemGroup extends PlatformMenuItem { PlatformMenuDelegate delegate, { required MenuItemSerializableIdGenerator getId, }) { - final List> result = >[]; - result.add({ - _kIdKey: getId(group), - _kIsDividerKey: true, - }); - for (final PlatformMenuItem item in group.members) { - result.addAll(item.toChannelRepresentation( - delegate, - getId: getId, - )); - } - result.add({ - _kIdKey: getId(group), - _kIsDividerKey: true, - }); - return result; + return >[ + {_kIdKey: getId(group), _kIsDividerKey: true}, + for (final PlatformMenuItem item in group.members) + ...item.toChannelRepresentation(delegate, getId: getId), + {_kIdKey: getId(group), _kIsDividerKey: true}, + ]; } @override diff --git a/packages/flutter/lib/src/widgets/pop_scope.dart b/packages/flutter/lib/src/widgets/pop_scope.dart index c8b31f60c8d5..8bbac4ce39b7 100644 --- a/packages/flutter/lib/src/widgets/pop_scope.dart +++ b/packages/flutter/lib/src/widgets/pop_scope.dart @@ -8,12 +8,29 @@ import 'framework.dart'; import 'navigator.dart'; import 'routes.dart'; +/// A callback type for informing that a navigation pop has been invoked, +/// whether or not it was handled successfully. +/// +/// Accepts a didPop boolean indicating whether or not back navigation +/// succeeded. +@Deprecated( + 'Use PopWithResultInvokedCallback instead. ' + 'This feature was deprecated after v3.22.0-12.0.pre.', +) +typedef PopInvokedCallback = void Function(bool didPop); + /// Manages back navigation gestures. /// +/// The generic type should match or be a supertype of the generic type of the +/// enclosing [Route]. For example, if the enclosing Route is a +/// `MaterialPageRoute`, you can define [PopScope] with `int` or any +/// supertype of `int`. +/// /// The [canPop] parameter disables back gestures when set to `false`. /// -/// The [onPopInvoked] parameter reports when pop navigation was attempted, and -/// `didPop` indicates whether or not the navigation was successful. +/// The [onPopInvokedWithResult] parameter reports when pop navigation was attempted, and +/// `didPop` indicates whether or not the navigation was successful. The +/// `result` contains the pop result. /// /// Android has a system back gesture that is a swipe inward from near the edge /// of the screen. It is recognized by Android before being passed to Flutter. @@ -22,15 +39,15 @@ import 'routes.dart'; /// back gesture. /// /// If [canPop] is false, then a system back gesture will not pop the route off -/// of the enclosing [Navigator]. [onPopInvoked] will still be called, and +/// of the enclosing [Navigator]. [onPopInvokedWithResult] will still be called, and /// `didPop` will be `false`. On iOS when using [CupertinoRouteTransitionMixin] /// with [canPop] set to false, no gesture will be detected at all, so -/// [onPopInvoked] will not be called. Programmatically attempting pop -/// navigation will also result in a call to [onPopInvoked], with `didPop` +/// [onPopInvokedWithResult] will not be called. Programmatically attempting pop +/// navigation will also result in a call to [onPopInvokedWithResult], with `didPop` /// indicating success or failure. /// /// If [canPop] is true, then a system back gesture will cause the enclosing -/// [Navigator] to receive a pop as usual. [onPopInvoked] will be called with +/// [Navigator] to receive a pop as usual. [onPopInvokedWithResult] will be called with /// `didPop` as true, unless the pop failed for reasons unrelated to /// [PopScope], in which case it will be false. /// @@ -41,30 +58,42 @@ import 'routes.dart'; /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** /// {@end-tool} /// +/// {@tool dartpad} +/// This sample demonstrates showing how to use PopScope to wrap widget that +/// may pop the page with a result. +/// +/// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.1.dart ** +/// {@end-tool} +/// /// See also: /// /// * [NavigatorPopHandler], which is a less verbose way to handle system back /// gestures in simple cases of nested [Navigator]s. -/// * [Form.canPop] and [Form.onPopInvoked], which can be used to handle system +/// * [Form.canPop] and [Form.onPopInvokedWithResult], which can be used to handle system /// back gestures in the case of a form with unsaved data. /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], /// which this widget uses to integrate with Flutter's navigation system. -class PopScope extends StatefulWidget { +class PopScope extends StatefulWidget { /// Creates a widget that registers a callback to veto attempts by the user to /// dismiss the enclosing [ModalRoute]. const PopScope({ super.key, required this.child, this.canPop = true, + this.onPopInvokedWithResult, + @Deprecated( + 'Use onPopInvokedWithResult instead. ' + 'This feature was deprecated after v3.22.0-12.0.pre.', + ) this.onPopInvoked, - }); + }) : assert(onPopInvokedWithResult == null || onPopInvoked == null, 'onPopInvoked is deprecated, use onPopInvokedWithResult'); /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; - /// {@template flutter.widgets.PopScope.onPopInvoked} + /// {@template flutter.widgets.PopScope.onPopInvokedWithResult} /// Called after a route pop was handled. /// {@endtemplate} /// @@ -78,11 +107,38 @@ class PopScope extends StatefulWidget { /// indicates whether or not the back navigation actually happened /// successfully. /// + /// The `result` contains the pop result. + /// /// See also: /// /// * [Route.onPopInvoked], which is similar. + final PopInvokedWithResultCallback? onPopInvokedWithResult; + + /// Called after a route pop was handled. + /// + /// It's not possible to prevent the pop from happening at the time that this + /// method is called; the pop has already happened. Use [canPop] to + /// disable pops in advance. + /// + /// This will still be called even when the pop is canceled. A pop is canceled + /// when the relevant [Route.popDisposition] returns false, such as when + /// [canPop] is set to false on a [PopScope]. The `didPop` parameter + /// indicates whether or not the back navigation actually happened + /// successfully. + @Deprecated( + 'Use onPopInvokedWithResult instead. ' + 'This feature was deprecated after v3.22.0-12.0.pre.', + ) final PopInvokedCallback? onPopInvoked; + void _callPopInvoked(bool didPop, T? result) { + if (onPopInvokedWithResult != null) { + onPopInvokedWithResult!(didPop, result); + return; + } + onPopInvoked?.call(didPop); + } + /// {@template flutter.widgets.PopScope.canPop} /// When false, blocks the current route from being popped. /// @@ -99,14 +155,16 @@ class PopScope extends StatefulWidget { final bool canPop; @override - State createState() => _PopScopeState(); + State> createState() => _PopScopeState(); } -class _PopScopeState extends State implements PopEntry { +class _PopScopeState extends State> implements PopEntry { ModalRoute? _route; @override - PopInvokedCallback? get onPopInvoked => widget.onPopInvoked; + void onPopInvoked(bool didPop, T? result) { + widget._callPopInvoked(didPop, result); + } @override late final ValueNotifier canPopNotifier; @@ -129,7 +187,7 @@ class _PopScopeState extends State implements PopEntry { } @override - void didUpdateWidget(PopScope oldWidget) { + void didUpdateWidget(PopScope oldWidget) { super.didUpdateWidget(oldWidget); canPopNotifier.value = widget.canPop; } diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index 7818bbda088a..b0f11c82cc52 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -624,9 +624,7 @@ class SliverReorderableListState extends State with Ticke late ScrollableState _scrollable; Axis get _scrollDirection => axisDirectionToAxis(_scrollable.axisDirection); - bool get _reverse => - _scrollable.axisDirection == AxisDirection.up || - _scrollable.axisDirection == AxisDirection.left; + bool get _reverse => axisDirectionIsReversed(_scrollable.axisDirection); @override void didChangeDependencies() { diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 277a5fb6c553..89e3af2d8ed1 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -604,7 +604,7 @@ abstract interface class PredictiveBackRoute { /// Handles a predictive back gesture ending successfully. void handleCommitBackGesture(); - /// Handles a predictive back gesture ending in cancelation. + /// Handles a predictive back gesture ending in cancellation. void handleCancelBackGesture(); } @@ -1022,6 +1022,8 @@ class _ModalScopeState extends State<_ModalScope> { @override Widget build(BuildContext context) { + // Only top most route can participate in focus traversal. + focusScopeNode.skipTraversal = !widget.route.isCurrent; return AnimatedBuilder( animation: widget.route.restorationScopeId, builder: (BuildContext context, Widget? child) { @@ -1048,24 +1050,22 @@ class _ModalScopeState extends State<_ModalScope> { }, child: PrimaryScrollController( controller: primaryScrollController, - child: FocusScope( - node: focusScopeNode, // immutable - // Only top most route can participate in focus traversal. - skipTraversal: !widget.route.isCurrent, + child: FocusScope.withExternalFocusNode( + focusScopeNode: focusScopeNode, // immutable child: RepaintBoundary( - child: AnimatedBuilder( - animation: _listenable, // immutable + child: ListenableBuilder( + listenable: _listenable, // immutable builder: (BuildContext context, Widget? child) { return widget.route.buildTransitions( context, widget.route.animation!, widget.route.secondaryAnimation!, - // This additional AnimatedBuilder is include because if the + // This additional ListenableBuilder is include because if the // value of the userGestureInProgressNotifier changes, it's // only necessary to rebuild the IgnorePointer widget and set // the focus node's ability to focus. - AnimatedBuilder( - animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier(false), + ListenableBuilder( + listenable: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier(false), builder: (BuildContext context, Widget? child) { final bool ignoreEvents = _shouldIgnoreFocusRequest; focusScopeNode.canRequestFocus = !ignoreEvents; @@ -1669,7 +1669,9 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute _willPopCallbacks = []; - final Set _popEntries = {}; + // Holding as Object? instead of T so that PopScope in this route can be + // declared with any supertype of T. + final Set> _popEntries = >{}; /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with /// [addScopedWillPopCallback] returns either false or null. If they all @@ -1717,14 +1719,14 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry in _popEntries) { if (!popEntry.canPopNotifier.value) { return RoutePopDisposition.doNotPop; } @@ -1734,9 +1736,9 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry in _popEntries) { + popEntry.onPopInvoked(didPop, result); } } @@ -1793,7 +1795,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry) { _popEntries.add(popEntry); popEntry.canPopNotifier.addListener(_handlePopEntryChange); _handlePopEntryChange(); @@ -1804,7 +1806,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry) { _popEntries.remove(popEntry); popEntry.canPopNotifier.removeListener(_handlePopEntryChange); _handlePopEntryChange(); @@ -2413,7 +2415,9 @@ typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animatio /// /// Accepts a didPop boolean indicating whether or not back navigation /// succeeded. -typedef PopInvokedCallback = void Function(bool didPop); +/// +/// The `result` contains the pop result. +typedef PopInvokedWithResultCallback = void Function(bool didPop, T? result); /// Allows listening to and preventing pops. /// @@ -2425,9 +2429,9 @@ typedef PopInvokedCallback = void Function(bool didPop); /// * [PopScope], which provides similar functionality in a widget. /// * [ModalRoute.registerPopEntry], which unregisters instances of this. /// * [ModalRoute.unregisterPopEntry], which unregisters instances of this. -abstract class PopEntry { - /// {@macro flutter.widgets.PopScope.onPopInvoked} - PopInvokedCallback? get onPopInvoked; +abstract class PopEntry { + /// {@macro flutter.widgets.PopScope.onPopInvokedWithResult} + void onPopInvoked(bool didPop, T? result); /// {@macro flutter.widgets.PopScope.canPop} ValueListenable get canPopNotifier; diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 5a995b2feb58..eb6ae9bc67ee 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -729,13 +729,10 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { AxisDirection.right => (SemanticsAction.scrollLeft, SemanticsAction.scrollRight), }; - final Set actions = {}; - if (pixels > minScrollExtent) { - actions.add(backward); - } - if (pixels < maxScrollExtent) { - actions.add(forward); - } + final Set actions = { + if (pixels > minScrollExtent) backward, + if (pixels < maxScrollExtent) forward, + }; if (setEquals(actions, _semanticActions)) { return; diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 780c3ed0a48d..74f27b95411a 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -2350,23 +2350,22 @@ class _HorizontalInnerDimensionState extends ScrollableState { ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit, RenderObject? targetRenderObject, }) { - final List> newFutures = >[]; - - newFutures.add(position.ensureVisible( - object, - alignment: alignment, - duration: duration, - curve: curve, - alignmentPolicy: alignmentPolicy, - )); - - newFutures.add(verticalScrollable.position.ensureVisible( - object, - alignment: alignment, - duration: duration, - curve: curve, - alignmentPolicy: alignmentPolicy, - )); + final List> newFutures = >[ + position.ensureVisible( + object, + alignment: alignment, + duration: duration, + curve: curve, + alignmentPolicy: alignmentPolicy, + ), + verticalScrollable.position.ensureVisible( + object, + alignment: alignment, + duration: duration, + curve: curve, + alignmentPolicy: alignmentPolicy, + ), + ]; return (newFutures, verticalScrollable); } diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 538267f1429d..09f5b35abffd 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -2213,13 +2213,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai /// Copies the selected contents of all [Selectable]s. @override SelectedContent? getSelectedContent() { - final List selections = []; - for (final Selectable selectable in selectables) { - final SelectedContent? data = selectable.getSelectedContent(); - if (data != null) { - selections.add(data); - } - } + final List selections = [ + for (final Selectable selectable in selectables) + if (selectable.getSelectedContent() case final SelectedContent data) data, + ]; if (selections.isEmpty) { return null; } diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 3ee9541056f6..fa344fbfc19b 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -622,7 +622,7 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S /// /// {@tool dartpad} /// In the following example, when a key combination results in a question mark, -/// the counter is increased: +/// the [SnackBar] gets shown: /// /// ** See code in examples/api/lib/widgets/shortcuts/character_activator.0.dart ** /// {@end-tool} diff --git a/packages/flutter/lib/src/widgets/tap_region.dart b/packages/flutter/lib/src/widgets/tap_region.dart index f4bdf8d89eaa..d00ec9d40841 100644 --- a/packages/flutter/lib/src/widgets/tap_region.dart +++ b/packages/flutter/lib/src/widgets/tap_region.dart @@ -254,18 +254,14 @@ class RenderTapRegionSurface extends RenderProxyBoxWithHitTestBehavior implement // groups of regions that were not hit. final Set hitRegions = _getRegionsHit(_registeredRegions, result.path).cast().toSet(); - final Set insideRegions = {}; assert(_tapRegionDebug('Tap event hit ${hitRegions.length} descendants.')); - for (final RenderTapRegion region in hitRegions) { - if (region.groupId == null) { - insideRegions.add(region); - continue; - } - // Add all grouped regions to the insideRegions so that groups act as a - // single region. - insideRegions.addAll(_groupIdToRegions[region.groupId]!); - } + final Set insideRegions = { + for (final RenderTapRegion region in hitRegions) + if (region.groupId == null) region + // Adding all grouped regions, so they act as a single region. + else ..._groupIdToRegions[region.groupId]!, + }; // If they're not inside, then they're outside. final Set outsideRegions = _registeredRegions.difference(insideRegions); @@ -292,15 +288,12 @@ class RenderTapRegionSurface extends RenderProxyBoxWithHitTestBehavior implement } // Returns the registered regions that are in the hit path. - Iterable _getRegionsHit(Set detectors, Iterable hitTestPath) { - final Set hitRegions = {}; - for (final HitTestEntry entry in hitTestPath) { - final HitTestTarget target = entry.target; - if (_registeredRegions.contains(target)) { - hitRegions.add(target); - } - } - return hitRegions; + Set _getRegionsHit(Set detectors, Iterable hitTestPath) { + return { + for (final HitTestEntry entry in hitTestPath) + if (entry.target case final HitTestTarget target) + if (_registeredRegions.contains(target)) target, + }; } } diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 9c7e16e64475..c0cf9d7d38f6 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -2565,12 +2565,10 @@ class TextSelectionGestureDetectorBuilder { final Offset editableOffset = renderEditable.maxLines == 1 ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) : Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset); - final double effectiveScrollPosition = _scrollPosition - _dragStartScrollOffset; - final bool scrollingOnVerticalAxis = _scrollDirection == AxisDirection.up || _scrollDirection == AxisDirection.down; - final Offset scrollableOffset = Offset( - !scrollingOnVerticalAxis ? effectiveScrollPosition : 0.0, - scrollingOnVerticalAxis ? effectiveScrollPosition : 0.0, - ); + final Offset scrollableOffset = switch (axisDirectionToAxis(_scrollDirection ?? AxisDirection.left)) { + Axis.horizontal => Offset(_scrollPosition - _dragStartScrollOffset, 0.0), + Axis.vertical => Offset(0.0, _scrollPosition - _dragStartScrollOffset), + }; switch (defaultTargetPlatform) { case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index 16bd0f0c58de..fc757f8c17a6 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -126,6 +126,9 @@ class _AnimatedState extends State { } void _handleChange() { + if (!mounted) { + return; + } setState(() { // The listenable's state is our build state, and it changed already. }); @@ -482,15 +485,12 @@ class SizeTransition extends AnimatedWidget { @override Widget build(BuildContext context) { - final AlignmentDirectional alignment; - if (axis == Axis.vertical) { - alignment = AlignmentDirectional(-1.0, axisAlignment); - } else { - alignment = AlignmentDirectional(axisAlignment, -1.0); - } return ClipRect( child: Align( - alignment: alignment, + alignment: switch (axis) { + Axis.horizontal => AlignmentDirectional(axisAlignment, -1.0), + Axis.vertical => AlignmentDirectional(-1.0, axisAlignment), + }, heightFactor: axis == Axis.vertical ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, widthFactor: axis == Axis.horizontal ? math.max(sizeFactor.value, 0.0) : fixedCrossAxisSizeFactor, child: child, diff --git a/packages/flutter/lib/src/widgets/view.dart b/packages/flutter/lib/src/widgets/view.dart index ad7ab7f7aaff..728a09d46973 100644 --- a/packages/flutter/lib/src/widgets/view.dart +++ b/packages/flutter/lib/src/widgets/view.dart @@ -699,17 +699,14 @@ class _MultiChildComponentElement extends Element { @override List debugDescribeChildren() { - final List children = []; - if (_childElement != null) { - children.add(_childElement!.toDiagnosticsNode()); - } - for (int i = 0; i < _viewElements.length; i++) { - children.add(_viewElements[i].toDiagnosticsNode( - name: 'view ${i + 1}', - style: DiagnosticsTreeStyle.offstage, - )); - } - return children; + return [ + if (_childElement != null) _childElement!.toDiagnosticsNode(), + for (int i = 0; i < _viewElements.length; i++) + _viewElements[i].toDiagnosticsNode( + name: 'view ${i + 1}', + style: DiagnosticsTreeStyle.offstage, + ), + ]; } } diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index d578e594cb85..e04c121e7c56 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -960,17 +960,11 @@ mixin WidgetInspectorService { registerServiceExtension( name: name, callback: (Map parameters) async { - final List args = []; - int index = 0; - while (true) { - final String name = 'arg$index'; - if (parameters.containsKey(name)) { - args.add(parameters[name]!); - } else { - break; - } - index++; - } + int index; + final List args = [ + for (index = 0; parameters['arg$index'] != null; index++) + parameters['arg$index']!, + ]; // Verify that the only arguments other than perhaps 'isolateId' are // arguments we have already handled. assert(index == parameters.length || (index == parameters.length - 1 && parameters.containsKey('isolateId'))); @@ -3408,27 +3402,16 @@ class _Location { final String? name; Map toJsonMap() { - final Map json = { + return { 'file': file, 'line': line, 'column': column, + if (name != null) 'name': name, }; - if (name != null) { - json['name'] = name; - } - return json; } @override - String toString() { - final List parts = []; - if (name != null) { - parts.add(name!); - } - parts.add(file); - parts..add('$line')..add('$column'); - return parts.join(':'); - } + String toString() => [if (name != null) name!, file, '$line', '$column'].join(':'); } bool _isDebugCreator(DiagnosticsNode node) => node is DiagnosticsDebugCreator; diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index b90d3060f364..ca194f0b1abc 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: characters: 1.3.0 collection: 1.18.0 material_color_utilities: 0.11.1 - meta: 1.12.0 + meta: 1.14.0 vector_math: 2.1.4 sky_engine: sdk: flutter @@ -44,8 +44,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 7643 +# PUBSPEC CHECKSUM: 7447 diff --git a/packages/flutter/test/animation/live_binding_test.dart b/packages/flutter/test/animation/live_binding_test.dart index 33692e66eed8..3d155a4cad96 100644 --- a/packages/flutter/test/animation/live_binding_test.dart +++ b/packages/flutter/test/animation/live_binding_test.dart @@ -9,7 +9,6 @@ library; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { /* @@ -79,8 +78,6 @@ void main() { }, skip: true); // Typically skip: isBrowser https://github.com/flutter/flutter/issues/42767 testWidgets('Should show event indicator for pointer events with setSurfaceSize', - // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), (WidgetTester tester) async { final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(200, 200), allLayers: true); addTearDown(animationSheet.dispose); @@ -113,6 +110,7 @@ void main() { ); await tester.binding.setSurfaceSize(const Size(300, 300)); + addTearDown(() => tester.binding.setSurfaceSize(null)); await tester.pumpWidget(target(recording: false)); await tester.pumpFrames(target(), const Duration(milliseconds: 50)); diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart index 181e5d014441..f05b299b3a44 100644 --- a/packages/flutter/test/cupertino/route_test.dart +++ b/packages/flutter/test/cupertino/route_test.dart @@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/semantics_tester.dart'; @@ -2431,6 +2432,53 @@ void main() { expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0); }); }); + + testWidgets( + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: ['CurvedAnimation']), + 'Fullscreen route does not leak CurveAnimation', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return CupertinoButton( + child: const Text('Button'), + onPressed: () { + Navigator.push(context, CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return Column( + children: [ + const Placeholder(), + CupertinoButton( + child: const Text('Close'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ); + }, + )); + }, + ); + }, + ), + ), + ); + + // Enter animation. + await tester.tap(find.text('Button')); + await tester.pump(); + + await tester.pump(const Duration(milliseconds: 400)); + + // Exit animation + await tester.tap(find.text('Close')); + await tester.pump(); + + await tester.pump(const Duration(milliseconds: 400)); + }); } class MockNavigatorObserver extends NavigatorObserver { diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index 4e2ac52ca174..df81155716f9 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -831,8 +831,6 @@ void main() { testWidgets('A controller can control more than one CupertinoTabScaffold, ' 'removal of listeners does not break the controller', - // TODO(polina-c): dispose TabController, https://github.com/flutter/flutter/issues/144910 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), (WidgetTester tester) async { final List tabsPainted0 = []; final List tabsPainted1 = []; @@ -1277,10 +1275,7 @@ void main() { .setMockMethodCallHandler(SystemChannels.platform, null); }); - testWidgets('System back navigation inside of tabs', - // TODO(polina-c): dispose TabController, https://github.com/flutter/flutter/issues/144910 - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), - (WidgetTester tester) async { + testWidgets('System back navigation inside of tabs', (WidgetTester tester) async { await tester.pumpWidget( CupertinoApp( home: MediaQuery( diff --git a/packages/flutter/test/cupertino/tab_test.dart b/packages/flutter/test/cupertino/tab_test.dart index 5acf173cd634..7188777509ac 100644 --- a/packages/flutter/test/cupertino/tab_test.dart +++ b/packages/flutter/test/cupertino/tab_test.dart @@ -305,7 +305,7 @@ void main() { BottomNavigationBarItem(label: '', icon: Text('2')) ], ), - tabBuilder: (_, int i) => PopScope( + tabBuilder: (_, int i) => PopScope( canPop: false, child: CupertinoTabView( navigatorKey: key, diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart index 5d9717d258e7..3fe50706fc08 100644 --- a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart +++ b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart @@ -204,12 +204,7 @@ void main() { // Adding 7 more children overflows onto a third page. setState(() { - children.add(const TestBox()); - children.add(const TestBox()); - children.add(const TestBox()); - children.add(const TestBox()); - children.add(const TestBox()); - children.add(const TestBox()); + children.addAll(List.filled(6, const TestBox())); }); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(7)); diff --git a/packages/flutter/test/flutter_test_config.dart b/packages/flutter/test/flutter_test_config.dart index 93ca5ff0af9b..4263ee110c49 100644 --- a/packages/flutter/test/flutter_test_config.dart +++ b/packages/flutter/test/flutter_test_config.dart @@ -52,8 +52,6 @@ Future testExecutable(FutureOr Function() testMain) { classes: [ // TODO(polina-c): CurvedAnimation is leaking, https://github.com/flutter/flutter/issues/145600 [leaks-to-clean] 'CurvedAnimation', - // TODO(polina-c): _NullElement is leaking, https://github.com/flutter/flutter/issues/145602 [leaks-to-clean] - '_NullElement', ], ); } diff --git a/packages/flutter/test/gestures/recognizer_test.dart b/packages/flutter/test/gestures/recognizer_test.dart index d26d216fec0e..7938624f5c30 100644 --- a/packages/flutter/test/gestures/recognizer_test.dart +++ b/packages/flutter/test/gestures/recognizer_test.dart @@ -87,11 +87,13 @@ void main() { testGesture('cleans up state after winning arena', (GestureTester tester) { final List resolutions = []; final IndefiniteGestureRecognizer indefinite = IndefiniteGestureRecognizer(); + addTearDown(indefinite.dispose); final TestPrimaryPointerGestureRecognizer accepting = TestPrimaryPointerGestureRecognizer( GestureDisposition.accepted, onAcceptGesture: () => resolutions.add('accepted'), onRejectGesture: () => resolutions.add('rejected'), ); + addTearDown(accepting.dispose); expect(accepting.state, GestureRecognizerState.ready); expect(accepting.primaryPointer, isNull); expect(accepting.initialPosition, isNull); @@ -118,11 +120,13 @@ void main() { testGesture('cleans up state after losing arena', (GestureTester tester) { final List resolutions = []; final IndefiniteGestureRecognizer indefinite = IndefiniteGestureRecognizer(); + addTearDown(indefinite.dispose); final TestPrimaryPointerGestureRecognizer rejecting = TestPrimaryPointerGestureRecognizer( GestureDisposition.rejected, onAcceptGesture: () => resolutions.add('accepted'), onRejectGesture: () => resolutions.add('rejected'), ); + addTearDown(rejecting.dispose); expect(rejecting.state, GestureRecognizerState.ready); expect(rejecting.primaryPointer, isNull); expect(rejecting.initialPosition, isNull); @@ -156,6 +160,7 @@ void main() { testGesture('works properly when recycled', (GestureTester tester) { final List resolutions = []; final IndefiniteGestureRecognizer indefinite = IndefiniteGestureRecognizer(); + addTearDown(indefinite.dispose); final TestPrimaryPointerGestureRecognizer accepting = TestPrimaryPointerGestureRecognizer( GestureDisposition.accepted, preAcceptSlopTolerance: 15, @@ -163,6 +168,7 @@ void main() { onAcceptGesture: () => resolutions.add('accepted'), onRejectGesture: () => resolutions.add('rejected'), ); + addTearDown(accepting.dispose); // Send one complete pointer sequence indefinite.addPointer(down); diff --git a/packages/flutter/test/gestures/serial_tap_test.dart b/packages/flutter/test/gestures/serial_tap_test.dart index f35cdff56ee9..5fcd9944d307 100644 --- a/packages/flutter/test/gestures/serial_tap_test.dart +++ b/packages/flutter/test/gestures/serial_tap_test.dart @@ -190,6 +190,7 @@ void main() { ..onRelease = () { recognizedRelease = true; }; + addTearDown(release.dispose); release.addPointer(down1); serial.addPointer(down1); @@ -207,6 +208,7 @@ void main() { ..onRelease = () { recognizedRelease = true; }; + addTearDown(release.dispose); serial.addPointer(down1); release.addPointer(down1); @@ -220,6 +222,7 @@ void main() { testGesture('Fires cancel if competing recognizer declares victory', (GestureTester tester) { final WinningGestureRecognizer winner = WinningGestureRecognizer(); + addTearDown(winner.dispose); winner.addPointer(down1); serial.addPointer(down1); tester.closeArena(1); diff --git a/packages/flutter/test/material/badge_test.dart b/packages/flutter/test/material/badge_test.dart index 23cf465e235f..e426100989aa 100644 --- a/packages/flutter/test/material/badge_test.dart +++ b/packages/flutter/test/material/badge_test.dart @@ -306,19 +306,19 @@ void main() { expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 0, 200, 16, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerLeft)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100 - 8, 16, 100 + 8, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100, 16, 100 + 16, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerRight)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 100 - 8, 200, 100 + 8, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 100, 200, 100 + 16, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomLeft)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200 - 16, 16, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200, 16, 200 + 16, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomCenter)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 8, 200 - 16, 100 + 8, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 8, 200, 100 + 8, 200 + 16, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomRight)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 200 - 16, 200, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 200, 200, 200 + 16, badgeRadius))); const Offset offset = Offset(5, 10); @@ -332,19 +332,19 @@ void main() { expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 0, 200, 16, badgeRadius).shift(offset))); await tester.pumpWidget(buildFrame(Alignment.centerLeft, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100 - 8, 16, 100 + 8, badgeRadius).shift(offset))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100, 16, 100 + 16, badgeRadius).shift(offset))); await tester.pumpWidget(buildFrame(Alignment.centerRight, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 100 - 8, 200, 100 + 8, badgeRadius).shift(offset))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 100, 200, 100 + 16, badgeRadius).shift(offset))); await tester.pumpWidget(buildFrame(Alignment.bottomLeft, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200 - 16, 16, 200, badgeRadius).shift(offset))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200, 16, 200 + 16, badgeRadius).shift(offset))); await tester.pumpWidget(buildFrame(Alignment.bottomCenter, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 8, 200 - 16, 100 + 8, 200, badgeRadius).shift(offset))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 8, 200, 100 + 8, 200 + 16, badgeRadius).shift(offset))); await tester.pumpWidget(buildFrame(Alignment.bottomRight, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 200 - 16, 200, 200, badgeRadius).shift(offset))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 16, 200, 200, 200 + 16, badgeRadius).shift(offset))); }); testWidgets('Small Badge alignment', (WidgetTester tester) async { @@ -380,19 +380,19 @@ void main() { expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 0, 200, 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerLeft)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100 - 3, 6, 100 + 3, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100, 6, 100 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerRight)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 100 - 3, 200, 100 + 3, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 100, 200, 100 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomLeft)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200 - 6, 6, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200, 6, 200 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomCenter)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 3, 200 - 6, 100 + 3, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 3, 200, 100 + 3, 200 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomRight)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 200 - 6, 200, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 200, 200, 200 + 6, badgeRadius))); const Offset offset = Offset(5, 10); // Not used for smallSize Badges. @@ -406,18 +406,47 @@ void main() { expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 0, 200, 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerLeft, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100 - 3, 6, 100 + 3, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 100, 6, 100 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.centerRight, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 100 - 3, 200, 100 + 3, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 100, 200, 100 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomLeft, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200 - 6, 6, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, 200, 6, 200 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomCenter, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 3, 200 - 6, 100 + 3, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(100 - 3, 200, 100 + 3, 200 + 6, badgeRadius))); await tester.pumpWidget(buildFrame(Alignment.bottomRight, offset)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 200 - 6, 200, 200, badgeRadius))); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(200 - 6, 200, 200, 200 + 6, badgeRadius))); + }); + + testWidgets('Badge Larger than large size', (WidgetTester tester) async { + const Radius badgeRadius = Radius.circular(15); + + Widget buildFrame(Alignment alignment, [Offset offset = Offset.zero]) { + return MaterialApp( + theme: ThemeData.light(useMaterial3: true), + home: Align( + alignment: Alignment.topLeft, + child: Badge( + // LargeSize = 16, make content of badge bigger than the default. + label: Container(width: 30, height: 30, color: Colors.blue), + alignment: alignment, + offset: offset, + child: Container( + color: const Color(0xFF00FF00), + width: 200, + height: 200, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(Alignment.topLeft)); + final RenderBox box = tester.renderObject(find.byType(Badge)); + // Badge should scale with content + expect(box, paints..rrect(rrect: RRect.fromLTRBR(0, -7, 30 + 8, 23, badgeRadius))); }); } diff --git a/packages/flutter/test/material/badge_theme_test.dart b/packages/flutter/test/material/badge_theme_test.dart index 69317aa85fd6..c1088d38a8ee 100644 --- a/packages/flutter/test/material/badge_theme_test.dart +++ b/packages/flutter/test/material/badge_theme_test.dart @@ -103,7 +103,7 @@ void main() { // text width = 48 = fontSize * 4, text height = fontSize expect(tester.getSize(find.text('1234')), const Size(48, 12)); - expect(tester.getTopLeft(find.text('1234')), const Offset(33, 4)); + expect(tester.getTopLeft(find.text('1234')), const Offset(33, 2)); expect(tester.getSize(find.byType(Badge)), const Size(24, 24)); // default Icon size @@ -114,7 +114,7 @@ void main() { expect(textStyle.color, black); final RenderBox box = tester.renderObject(find.byType(Badge)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(28, 0, 86, 20, const Radius.circular(10)), color: green)); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(28, -2, 86, 18, const Radius.circular(10)), color: green)); }); @@ -150,13 +150,13 @@ void main() { ); expect(tester.getSize(find.text('1234')), const Size(48, 12)); - expect(tester.getTopLeft(find.text('1234')), const Offset(33, 4)); + expect(tester.getTopLeft(find.text('1234')), const Offset(33, 2)); expect(tester.getSize(find.byType(Badge)), const Size(24, 24)); // default Icon size expect(tester.getTopLeft(find.byType(Badge)), Offset.zero); final TextStyle textStyle = tester.renderObject(find.text('1234')).text.style!; expect(textStyle.fontSize, 12); expect(textStyle.color, black); final RenderBox box = tester.renderObject(find.byType(Badge)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(28, 0, 86, 20, const Radius.circular(10)), color: green)); + expect(box, paints..rrect(rrect: RRect.fromLTRBR(28, -2, 86, 18, const Radius.circular(10)), color: green)); }); } diff --git a/packages/flutter/test/material/banner_theme_test.dart b/packages/flutter/test/material/banner_theme_test.dart index d5a99531c65a..05fbcf121551 100644 --- a/packages/flutter/test/material/banner_theme_test.dart +++ b/packages/flutter/test/material/banner_theme_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { test('MaterialBannerThemeData copyWith, ==, hashCode basics', () { @@ -324,7 +325,10 @@ void main() { expect(find.byType(Divider), findsNothing); }); - testWidgets('MaterialBanner widget properties take priority over theme when presented by ScaffoldMessenger', (WidgetTester tester) async { + testWidgets('MaterialBanner widget properties take priority over theme when presented by ScaffoldMessenger', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { const Color backgroundColor = Colors.purple; const double elevation = 6.0; const TextStyle textStyle = TextStyle(color: Colors.green); diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart index dfe20d9e2e80..e265e0ad3453 100644 --- a/packages/flutter/test/material/bottom_navigation_bar_test.dart +++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart @@ -14,6 +14,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'package:vector_math/vector_math_64.dart' show Vector3; import '../widgets/semantics_tester.dart'; @@ -2187,7 +2188,10 @@ void main() { ); }); - testWidgets('BottomNavigationBar handles items.length changes', (WidgetTester tester) async { + testWidgets('BottomNavigationBar handles items.length changes', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/10322 Widget buildFrame(int itemCount) { @@ -2322,7 +2326,10 @@ void main() { ); } for (int pump = 1; pump < 9; pump++) { - testWidgets('pump $pump', (WidgetTester tester) async { + testWidgets('pump $pump', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']).withCreationStackTrace(), + (WidgetTester tester) async { await tester.pumpWidget(runTest()); await tester.tap(find.text('Green')); diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart index 101191a3bd65..20b65da0358d 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -7,6 +7,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/semantics_tester.dart'; @@ -1282,7 +1283,10 @@ void main() { await checkDragHandleAndColors(); }); - testWidgets('showModalBottomSheet does not use root Navigator by default', (WidgetTester tester) async { + testWidgets('showModalBottomSheet does not use root Navigator by default', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Scaffold( body: Navigator(onGenerateRoute: (RouteSettings settings) => MaterialPageRoute(builder: (_) { diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index e53908a9347c..70fbff590b8e 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -57,6 +57,13 @@ DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { ); } +TextStyle? getIconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon).first, matching: find.byType(RichText)), + ); + return iconRichText.text.style; +} + dynamic getRenderChip(WidgetTester tester) { if (!tester.any(findRenderChipElement())) { return null; @@ -1794,7 +1801,7 @@ void main() { expect(Focus.of(labelKey.currentContext!).hasPrimaryFocus, isTrue); }); - testWidgets('Material2 - Delete button creates non-centered, unique ripple when tapped', (WidgetTester tester) async { + testWidgets('Material2 - Delete button creates centered, unique ripple when tapped', (WidgetTester tester) async { final UniqueKey labelKey = UniqueKey(); final UniqueKey deleteButtonKey = UniqueKey(); @@ -1820,8 +1827,8 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); // There should be one unique ink ripple. - expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44)); - expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 1.44)); + expect(box, ripplePattern(Offset.zero, 1.44)); + expect(box, uniqueRipplePattern(Offset.zero, 1.44)); // There should be no tooltip. expect(findTooltipContainer('Delete'), findsNothing); @@ -1832,8 +1839,8 @@ void main() { // The ripple should grow, but the center should move, // Towards the center of the delete icon. - expect(box, ripplePattern(const Offset(5.0, 5.0), 4.32)); - expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 4.32)); + expect(box, ripplePattern(const Offset(2.0, 2.0), 4.32)); + expect(box, uniqueRipplePattern(const Offset(2.0, 2.0), 4.32)); // There should be no tooltip. expect(findTooltipContainer('Delete'), findsNothing); @@ -1925,8 +1932,8 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); // There should be one unique ink ripple. - expect(box, ripplePattern(const Offset(3.0, 3.0), 1.44)); - expect(box, uniqueRipplePattern(const Offset(3.0, 3.0), 1.44)); + expect(box, ripplePattern(Offset.zero, 1.44)); + expect(box, uniqueRipplePattern(Offset.zero, 1.44)); // There should be no tooltip. expect(findTooltipContainer('Delete'), findsNothing); @@ -1937,8 +1944,8 @@ void main() { // The ripple should grow, but the center should move, // Towards the center of the delete icon. - expect(box, ripplePattern(const Offset(5.0, 5.0), 4.32)); - expect(box, uniqueRipplePattern(const Offset(5.0, 5.0), 4.32)); + expect(box, ripplePattern(const Offset(2.0, 2.0), 4.32)); + expect(box, uniqueRipplePattern(const Offset(2.0, 2.0), 4.32)); // There should be no tooltip. expect(findTooltipContainer('Delete'), findsNothing); @@ -4476,7 +4483,7 @@ void main() { Rect avatarBox = tester.getRect(find.byKey(avatarKey)); expect(box.size, equals(const Size(128, 32.0 + 16.0))); expect(textBox.size, equals(const Size(56, 14))); - expect(iconBox.size, equals(const Size(24, 24))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(24, 24))); expect(textBox.top, equals(17)); expect(box.bottom - textBox.bottom, equals(17)); @@ -4491,7 +4498,7 @@ void main() { avatarBox = tester.getRect(find.byKey(avatarKey)); expect(box.size, equals(const Size(128, 60))); expect(textBox.size, equals(const Size(56, 14))); - expect(iconBox.size, equals(const Size(24, 24))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(24, 24))); expect(textBox.top, equals(23)); expect(box.bottom - textBox.bottom, equals(23)); @@ -4506,7 +4513,7 @@ void main() { avatarBox = tester.getRect(find.byKey(avatarKey)); expect(box.size, equals(const Size(128, 36))); expect(textBox.size, equals(const Size(56, 14))); - expect(iconBox.size, equals(const Size(24, 24))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(24, 24))); expect(textBox.top, equals(11)); expect(box.bottom - textBox.bottom, equals(11)); @@ -4523,7 +4530,7 @@ void main() { avatarBox = tester.getRect(find.byKey(avatarKey)); expect(box.size, equals(const Size(128, 36))); expect(textBox.size, equals(const Size(56, 14))); - expect(iconBox.size, equals(const Size(24, 24))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(24, 24))); expect(textBox.top, equals(11)); expect(box.bottom - textBox.bottom, equals(11)); @@ -4583,7 +4590,7 @@ void main() { expect(box.size.height, equals(32.0 + 16.0)); expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1)); expect(textBox.size.height, equals(20.0)); - expect(iconBox.size, equals(const Size(20, 20))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(18, 18))); expect(textBox.top, equals(14)); expect(box.bottom - textBox.bottom, equals(14)); @@ -4600,7 +4607,7 @@ void main() { expect(box.size.height, equals(60)); expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1)); expect(textBox.size.height, equals(20.0)); - expect(iconBox.size, equals(const Size(20, 20))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(18, 18))); expect(textBox.top, equals(20)); expect(box.bottom - textBox.bottom, equals(20)); @@ -4617,7 +4624,7 @@ void main() { expect(box.size.height, equals(36)); expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1)); expect(textBox.size.height, equals(20.0)); - expect(iconBox.size, equals(const Size(20, 20))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(18, 18))); expect(textBox.top, equals(8)); expect(box.bottom - textBox.bottom, equals(8)); @@ -4636,7 +4643,7 @@ void main() { expect(box.size.height, equals(36)); expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1)); expect(textBox.size.height, equals(20.0)); - expect(iconBox.size, equals(const Size(20, 20))); + expect(iconBox.size, equals(const Size(18, 18))); expect(avatarBox.size, equals(const Size(18, 18))); expect(textBox.top, equals(8)); expect(box.bottom - textBox.bottom, equals(8)); @@ -5705,6 +5712,75 @@ void main() { tester.getTopLeft(find.text('A').last).dy, ); }); + + testWidgets('ChipThemeData.iconTheme updates avatar and delete icons', (WidgetTester tester) async { + const Color iconColor = Color(0xffff00ff); + const double iconSize = 28.0; + const IconData avatarIcon = Icons.favorite; + const IconData deleteIcon = Icons.delete; + + await tester.pumpWidget(MaterialApp( + home: Material( + child: Center( + child: RawChip( + iconTheme: const IconThemeData( + color: iconColor, + size: iconSize, + ), + avatar: const Icon(Icons.favorite), + deleteIcon: const Icon(Icons.delete), + onDeleted: () { }, + label: const SizedBox(height: 100), + ), + ), + ), + )); + + // Test rendered icon size. + final RenderBox avatarIconBox = tester.renderObject(find.byIcon(avatarIcon)); + final RenderBox deleteIconBox = tester.renderObject(find.byIcon(deleteIcon)); + expect(avatarIconBox.size.width, equals(iconSize)); + expect(deleteIconBox.size.width, equals(iconSize)); + + // Test rendered icon color. + expect(getIconStyle(tester, avatarIcon)?.color, iconColor); + expect(getIconStyle(tester, deleteIcon)?.color, iconColor); + }); + + testWidgets('RawChip.deleteIconColor overrides iconTheme color', (WidgetTester tester) async { + const Color iconColor = Color(0xffff00ff); + const Color deleteIconColor = Color(0xffff00ff); + const IconData deleteIcon = Icons.delete; + + Widget buildChip({ Color? deleteIconColor, Color? iconColor }) { + return MaterialApp( + home: Material( + child: Center( + child: RawChip( + deleteIconColor: deleteIconColor, + iconTheme: IconThemeData(color: iconColor), + deleteIcon: const Icon(Icons.delete), + onDeleted: () { }, + label: const SizedBox(height: 100), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildChip(iconColor: iconColor)); + + // Test rendered icon color. + expect(getIconStyle(tester, deleteIcon)?.color, iconColor); + + await tester.pumpWidget(buildChip( + deleteIconColor: deleteIconColor, + iconColor: iconColor, + )); + + // Test rendered icon color. + expect(getIconStyle(tester, deleteIcon)?.color, deleteIconColor); + }); } class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index 5ce06eed5072..5d9cbefc42ca 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -44,6 +44,13 @@ DefaultTextStyle getLabelStyle(WidgetTester tester) { ); } +TextStyle? getIconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon).first, matching: find.byType(RichText)), + ); + return iconRichText.text.style; +} + void main() { test('ChipThemeData copyWith, ==, hashCode basics', () { expect(const ChipThemeData(), const ChipThemeData().copyWith()); @@ -1400,6 +1407,82 @@ void main() { final Offset labelTopRight = tester.getTopRight(find.byType(Container)); expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding); }); + + testWidgets('ChipThemeData.iconTheme updates avatar and delete icons', (WidgetTester tester) async { + const Color iconColor = Color(0xffff0000); + const double iconSize = 32.0; + const IconData avatarIcon = Icons.favorite; + const IconData deleteIcon = Icons.delete; + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(chipTheme: const ChipThemeData( + iconTheme: IconThemeData( + color: iconColor, + size: iconSize, + ), + )), + home: Material( + child: Center( + child: RawChip( + avatar: const Icon(Icons.favorite), + deleteIcon: const Icon(Icons.delete), + onDeleted: () { }, + label: const SizedBox(height: 100), + ), + ), + ), + )); + + // Test rendered icon size. + final RenderBox avatarIconBox = tester.renderObject(find.byIcon(avatarIcon)); + final RenderBox deleteIconBox = tester.renderObject(find.byIcon(deleteIcon)); + expect(avatarIconBox.size.width, equals(iconSize)); + expect(deleteIconBox.size.width, equals(iconSize)); + + // Test rendered icon color. + expect(getIconStyle(tester, avatarIcon)?.color, iconColor); + expect(getIconStyle(tester, deleteIcon)?.color, iconColor); + }); + + testWidgets('ChipThemeData.deleteIconColor overrides ChipThemeData.iconTheme color', (WidgetTester tester) async { + const Color iconColor = Color(0xffff00ff); + const Color deleteIconColor = Color(0xffff00ff); + const IconData deleteIcon = Icons.delete; + + Widget buildChip({ Color? deleteIconColor, Color? iconColor }) { + return MaterialApp( + theme: ThemeData( + chipTheme: ChipThemeData( + deleteIconColor: deleteIconColor, + iconTheme: IconThemeData(color: iconColor), + ) + ), + home: Material( + child: Center( + child: RawChip( + deleteIcon: const Icon(Icons.delete), + onDeleted: () { }, + label: const SizedBox(height: 100), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildChip(iconColor: iconColor)); + + // Test rendered icon color. + expect(getIconStyle(tester, deleteIcon)?.color, iconColor); + + await tester.pumpWidget(buildChip( + deleteIconColor: deleteIconColor, + iconColor: iconColor, + )); + + // Test rendered icon color. + expect(getIconStyle(tester, deleteIcon)?.color, deleteIconColor); + }); + } class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { diff --git a/packages/flutter/test/material/data_table_theme_test.dart b/packages/flutter/test/material/data_table_theme_test.dart index 96c894d8323a..2d9745e3ee00 100644 --- a/packages/flutter/test/material/data_table_theme_test.dart +++ b/packages/flutter/test/material/data_table_theme_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { test('DataTableThemeData copyWith, ==, hashCode basics', () { @@ -552,7 +553,10 @@ void main() { }); // This is a regression test for https://github.com/flutter/flutter/issues/143340. - testWidgets('DataColumn label can be centered with DataTableTheme.headingRowAlignment', (WidgetTester tester) async { + testWidgets('DataColumn label can be centered with DataTableTheme.headingRowAlignment', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { const double horizontalMargin = 24.0; Widget buildTable({ MainAxisAlignment? headingRowAlignment, bool sortEnabled = false }) { diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart index 330f65637b02..4095e7d562a3 100644 --- a/packages/flutter/test/material/date_picker_theme_test.dart +++ b/packages/flutter/test/material/date_picker_theme_test.dart @@ -539,7 +539,7 @@ void main() { ); final Material material = findDialogMaterial(tester); - expect(material.color, datePickerTheme.backgroundColor); //!! + expect(material.color, datePickerTheme.backgroundColor); expect(tester.widget(find.byType(Scaffold)).backgroundColor, datePickerTheme.rangePickerBackgroundColor); expect(material.elevation, datePickerTheme.rangePickerElevation); expect(material.shadowColor, datePickerTheme.rangePickerShadowColor); @@ -573,6 +573,67 @@ void main() { expect(inkFeatures, paints..circle(color: datePickerTheme.rangeSelectionOverlayColor?.resolve({}))); }); + testWidgets('Material2 - DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + datePickerTheme: datePickerTheme, + useMaterial3: false, + ), + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: DateRangePickerDialog( + firstDate: DateTime(2023), + lastDate: DateTime(2023, DateTime.january, 31), + initialDateRange: DateTimeRange( + start: DateTime(2023, DateTime.january, 17), + end: DateTime(2023, DateTime.january, 20), + ), + currentDate: DateTime(2023, DateTime.january, 23), + ), + ), + ), + ), + ), + ); + + final Material material = findDialogMaterial(tester); + expect(material.color, datePickerTheme.backgroundColor); + expect(tester.widget(find.byType(Scaffold)).backgroundColor, datePickerTheme.rangePickerBackgroundColor); + expect(material.elevation, datePickerTheme.rangePickerElevation); + expect(material.shadowColor, datePickerTheme.rangePickerShadowColor); + expect(material.surfaceTintColor, datePickerTheme.rangePickerSurfaceTintColor); + expect(material.shape, datePickerTheme.rangePickerShape); + + final AppBar appBar = tester.widget(find.byType(AppBar)); + expect(appBar.backgroundColor, datePickerTheme.rangePickerHeaderBackgroundColor); + + final Text selectRange = tester.widget(find.text('SELECT RANGE')); + expect(selectRange.style?.color, datePickerTheme.rangePickerHeaderForegroundColor); + expect(selectRange.style?.fontSize, datePickerTheme.rangePickerHeaderHelpStyle?.fontSize); + + final Text selectedDate = tester.widget(find.text('Jan 17')); + expect(selectedDate.style?.color, datePickerTheme.rangePickerHeaderForegroundColor); + expect(selectedDate.style?.fontSize, datePickerTheme.rangePickerHeaderHeadlineStyle?.fontSize); + + // Test the day overlay color. + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.text('16'))); + await tester.pumpAndSettle(); + expect(inkFeatures, paints..circle(color: datePickerTheme.dayOverlayColor?.resolve({}))); + + // Test the range selection overlay color. + await gesture.moveTo(tester.getCenter(find.text('18'))); + await tester.pumpAndSettle(); + expect(inkFeatures, paints..circle(color: datePickerTheme.rangeSelectionOverlayColor?.resolve({}))); + }); + testWidgets('Dividers use DatePickerThemeData.dividerColor', (WidgetTester tester) async { Future showPicker(WidgetTester tester, Size size) async { tester.view.physicalSize = size; diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart index 1fcc7aa8e611..0a6280831991 100644 --- a/packages/flutter/test/material/date_range_picker_test.dart +++ b/packages/flutter/test/material/date_range_picker_test.dart @@ -1576,7 +1576,7 @@ void main() { expect(appBar.actionsIconTheme, iconTheme); expect(appBar.elevation, null); expect(appBar.scrolledUnderElevation, null); - expect(appBar.backgroundColor, null); + expect(appBar.backgroundColor, theme.colorScheme.primary); }); }); diff --git a/packages/flutter/test/material/expansion_tile_test.dart b/packages/flutter/test/material/expansion_tile_test.dart index 536e4fd36ae2..70daebbb82fe 100644 --- a/packages/flutter/test/material/expansion_tile_test.dart +++ b/packages/flutter/test/material/expansion_tile_test.dart @@ -1149,7 +1149,10 @@ void main() { await tester.pumpAndSettle(); // Override the animation curve. - await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(curve: Easing.emphasizedDecelerate))); + await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle( + curve: Easing.emphasizedDecelerate, + reverseCurve: Easing.emphasizedAccelerate, + ))); await tester.pumpAndSettle(); // Test the overridden animation curve. @@ -1167,8 +1170,20 @@ void main() { expect(getHeight(expansionTileKey), 158.0); - // Tap to collapse the ExpansionTile. + // Test the overridden reverse (collapse) animation curve. await tester.tap(find.text('title')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration. + + expect(getHeight(expansionTileKey), closeTo(98.6, 0.1)); + + await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration. + + expect(getHeight(expansionTileKey), closeTo(73.4, 0.1)); + + await tester.pumpAndSettle(); // Advance the animation to the end. + + expect(getHeight(expansionTileKey), 58.0); // Test no animation. await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle.noAnimation)); diff --git a/packages/flutter/test/material/filter_chip_test.dart b/packages/flutter/test/material/filter_chip_test.dart index 535879c94fa4..c10032d70580 100644 --- a/packages/flutter/test/material/filter_chip_test.dart +++ b/packages/flutter/test/material/filter_chip_test.dart @@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'feedback_tester.dart'; @@ -956,7 +957,10 @@ void main() { expect(getIconData(tester).color, theme.iconTheme.color?.withAlpha(0xde)); }); - testWidgets('Customize FilterChip delete button', (WidgetTester tester) async { + testWidgets('Customize FilterChip delete button', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { Widget buildChip({ Widget? deleteIcon, Color? deleteIconColor, @@ -1207,7 +1211,7 @@ void main() { expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border); expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); - // Calculate the distnance between avatar and label. + // Calculate the distance between avatar and label. Offset labelTopLeft = tester.getTopLeft(find.byType(Container)); expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding); @@ -1223,7 +1227,7 @@ void main() { expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border); expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); - // Calculate the distnance between avatar and label. + // Calculate the distance between avatar and label. labelTopLeft = tester.getTopLeft(find.byType(Container)); expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding); }); diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 2a74cf2764d9..38236b7d1447 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -7,6 +7,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -27,6 +28,10 @@ void main() { mockOnPressedFunction = MockOnPressedFunction(); }); + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('test icon is findable by key', (WidgetTester tester) async { const ValueKey key = ValueKey('icon-button'); await tester.pumpWidget( @@ -1222,10 +1227,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1234,12 +1235,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1249,7 +1250,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.1))); focusNode.dispose(); }); @@ -1363,10 +1364,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1375,12 +1372,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1390,7 +1387,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onPrimary.withOpacity(0.1))); focusNode.dispose(); }); @@ -1619,10 +1616,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1631,12 +1624,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1646,7 +1639,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSecondaryContainer.withOpacity(0.1))); focusNode.dispose(); }); @@ -1875,10 +1868,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - // Hovered. final Offset center = tester.getCenter(find.byType(IconButton)); final TestGesture gesture = await tester.createGesture( @@ -1887,12 +1876,12 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); // Remove pressed and hovered states await gesture.up(); await tester.pumpAndSettle(); @@ -1902,7 +1891,7 @@ void main() { // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08))); focusNode.dispose(); }); @@ -2586,6 +2575,156 @@ void main() { expect(box.size, equals(const Size(48, 48))); }); + testWidgets('IconButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); + + testWidgets('IconButton.styleFrom highlight, hover, focus colors overrides overlayColor', (WidgetTester tester) async { + const Color hoverColor = Color(0xff0000f2); + const Color highlightColor = Color(0xff0000f1); + const Color focusColor = Color(0xff0000f3); + const Color overlayColor = Color(0xffff0000); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom( + hoverColor: hoverColor, + highlightColor: highlightColor, + focusColor: focusColor, + overlayColor: overlayColor, + ), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: hoverColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: hoverColor) + ..rect(color: highlightColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: focusColor)); + }); + + testWidgets('IconButton.styleFrom with transparent overlayColor', (WidgetTester tester) async { + const Color overlayColor = Colors.transparent; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor) + ..rect(color: overlayColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + }); + group('IconTheme tests in Material 3', () { testWidgets('IconTheme overrides default values in M3', (WidgetTester tester) async { // Theme's IconTheme diff --git a/packages/flutter/test/material/icon_button_theme_test.dart b/packages/flutter/test/material/icon_button_theme_test.dart index 4acf1807f82b..dce90d24a624 100644 --- a/packages/flutter/test/material/icon_button_theme_test.dart +++ b/packages/flutter/test/material/icon_button_theme_test.dart @@ -2,10 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + test('IconButtonThemeData lerp special cases', () { expect(IconButtonThemeData.lerp(null, null, 0), null); const IconButtonThemeData data = IconButtonThemeData(); @@ -254,4 +260,57 @@ void main() { material = tester.widget(buttonMaterialFinder); expect(material.shadowColor, shadowColor); }); + + testWidgets('IconButtonTheme IconButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + overlayColor: overlayColor, + ), + ), + child: IconButton( + onPressed: () { }, + icon: const Icon(Icons.add), + ), + ), + ), + ), + ), + ); + + // Hovered. + final Offset center = tester.getCenter(find.byType(IconButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); } diff --git a/packages/flutter/test/material/inherited_theme_test.dart b/packages/flutter/test/material/inherited_theme_test.dart index a36a6fec1c55..31ebd1e64682 100644 --- a/packages/flutter/test/material/inherited_theme_test.dart +++ b/packages/flutter/test/material/inherited_theme_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { testWidgets('Theme.wrap()', (WidgetTester tester) async { @@ -145,7 +146,10 @@ void main() { await tester.pumpAndSettle(); // menu route animation }); - testWidgets('Material3 - PopupMenuTheme.wrap()', (WidgetTester tester) async { + testWidgets('Material3 - PopupMenuTheme.wrap()', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: ['CurvedAnimation']), + (WidgetTester tester) async { const TextStyle textStyle = TextStyle(fontSize: 24.0, color: Color(0xFF0000FF)); Widget buildFrame() { diff --git a/packages/flutter/test/material/input_chip_test.dart b/packages/flutter/test/material/input_chip_test.dart index e692602668aa..20a48fd5d8fa 100644 --- a/packages/flutter/test/material/input_chip_test.dart +++ b/packages/flutter/test/material/input_chip_test.dart @@ -10,6 +10,7 @@ library; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; /// Adds the basic requirements for a Chip. Widget wrapForChip({ @@ -456,7 +457,10 @@ void main() { await expectLater(find.byType(RawChip), matchesGoldenFile('input_chip.disabled.delete_button.png')); }); - testWidgets('Delete button tooltip is not shown on disabled InputChip', (WidgetTester tester) async { + testWidgets('Delete button tooltip is not shown on disabled InputChip', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { Widget buildChip({ bool enabled = true }) { return wrapForChip( child: InputChip( @@ -521,7 +525,7 @@ void main() { expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border); expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); - // Calculate the distnance between avatar and label. + // Calculate the distance between avatar and label. Offset labelTopLeft = tester.getTopLeft(find.byType(Container)); expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding); @@ -537,7 +541,7 @@ void main() { expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border); expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); - // Calculate the distnance between avatar and label. + // Calculate the distance between avatar and label. labelTopLeft = tester.getTopLeft(find.byType(Container)); expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding); }); diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 7a68461f5113..4efdd2a05b14 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -350,7 +350,7 @@ void main() { ..path( style: PaintingStyle.fill, color: theme.colorScheme.surfaceContainerHighest, - ) + ), ); }); @@ -409,7 +409,7 @@ void main() { ..path( style: PaintingStyle.fill, color: theme.colorScheme.onSurface.withOpacity(0.04), - ) + ), ); }); @@ -470,7 +470,7 @@ void main() { ..path( style: PaintingStyle.fill, color: Color.alphaBlend(theme.hoverColor, theme.colorScheme.surfaceContainerHighest), - ) + ), ); }); @@ -530,7 +530,32 @@ void main() { ..path( style: PaintingStyle.fill, color: theme.colorScheme.surfaceContainerHighest, - ) + ), + ); + }); + + testWidgets('container has correct color when focused and hovered', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/146573. + await tester.pumpWidget( + buildInputDecorator( + isFocused: true, + isHovering: true, + decoration: const InputDecoration( + filled: true, + labelText: labelText, + helperText: helperText, + ), + ), + ); + + final ThemeData theme = Theme.of(tester.element(findDecorator())); + final Color focusColor = theme.colorScheme.surfaceContainerHighest; + final Color hoverColor = theme.hoverColor; + expect(findBorderPainter(), paints + ..path( + style: PaintingStyle.fill, + color: Color.alphaBlend(hoverColor, focusColor), + ), ); }); @@ -607,7 +632,7 @@ void main() { ..path( style: PaintingStyle.fill, color: theme.colorScheme.surfaceContainerHighest, - ) + ), ); }); @@ -729,7 +754,7 @@ void main() { expect(findBorderPainter(), paints ..path( style: PaintingStyle.stroke, - ) + ), ); }); @@ -784,7 +809,7 @@ void main() { expect(findBorderPainter(), paints ..path( style: PaintingStyle.stroke, - ) + ), ); }); @@ -840,7 +865,7 @@ void main() { expect(findBorderPainter(), paints ..path( style: PaintingStyle.stroke, - ) + ), ); }); @@ -896,7 +921,7 @@ void main() { expect(findBorderPainter(), paints ..path( style: PaintingStyle.stroke, - ) + ), ); }); @@ -969,7 +994,7 @@ void main() { expect(findBorderPainter(), paints ..path( style: PaintingStyle.stroke, - ) + ), ); }); @@ -3263,7 +3288,7 @@ void main() { ); // Label and input are horizontally aligned despite `alignLabelWithHint` being false (default value). - // The reason is that `alignLabelWithHint` was initially intended for vertical alignement only. + // The reason is that `alignLabelWithHint` was initially intended for vertical alignment only. // See https://github.com/flutter/flutter/pull/24993 which introduced `alignLabelWithHint` parameter. // See https://github.com/flutter/flutter/pull/115409 which used `alignLabelWithHint` for // horizontal alignment in outlined text field. @@ -3881,6 +3906,23 @@ void main() { expect(getDecoratorRect(tester).height, closeTo(containerHeight + helperGap + errorHeight * numberOfLines, 0.25)); }); + testWidgets('Error height is not limited by default', (WidgetTester tester) async { + const int numberOfLines = 3; + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration( + labelText: 'label', + errorText: threeLines, + filled: true, + ), + ), + ); + + final Rect errorRect = tester.getRect(find.text(threeLines)); + expect(errorRect.height, closeTo(errorHeight * numberOfLines, 0.25)); + expect(getDecoratorRect(tester).height, closeTo(containerHeight + helperGap + errorHeight * numberOfLines, 0.25)); + }); + testWidgets('Helper height grows to accommodate helper text', (WidgetTester tester) async { const int maxLines = 3; await tester.pumpWidget( @@ -3935,6 +3977,23 @@ void main() { expect(helperRect.height, closeTo(helperHeight * numberOfLines, 0.25)); expect(getDecoratorRect(tester).height, closeTo(containerHeight + helperGap + helperHeight * numberOfLines, 0.25)); }); + + testWidgets('Helper height is not limited by default', (WidgetTester tester) async { + const int numberOfLines = 3; + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration( + labelText: 'label', + helperText: threeLines, + filled: true, + ), + ), + ); + + final Rect helperRect = tester.getRect(find.text(threeLines)); + expect(helperRect.height, closeTo(helperHeight * numberOfLines, 0.25)); + expect(getDecoratorRect(tester).height, closeTo(containerHeight + helperGap + helperHeight * numberOfLines, 0.25)); + }); }); group('Helper widget', () { diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 3490734468a3..5ff7ef894265 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -166,6 +166,10 @@ void main() { ); } + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('Menu responds to density changes', (WidgetTester tester) async { Widget buildMenu({VisualDensity? visualDensity = VisualDensity.standard}) { return MaterialApp( @@ -2494,6 +2498,40 @@ void main() { final AssertionError exception = tester.takeException() as AssertionError; expect(exception, isAssertionError); }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/99933 + + testWidgets('MenuItemButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: MenuItemButton( + style: MenuItemButton.styleFrom(overlayColor: overlayColor), + onPressed: () {}, + child: const Text('MenuItem'), + ), + ), + )); + + // Hovered. + final Offset center = tester.getCenter(find.byType(MenuItemButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + }); }); group('Layout', () { @@ -3709,6 +3747,45 @@ void main() { ); expect(intrinsicWidthFinder, findsOneWidget); }); + + testWidgets('SubmenuButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff00ff); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: SubmenuButton( + style: SubmenuButton.styleFrom(overlayColor: overlayColor), + menuChildren: [ + MenuItemButton( + onPressed: () {}, + child: const Text('MenuItemButton'), + ), + ], + child: const Text('Submenu'), + ), + ), + )); + + // Hovered. + final Offset center = tester.getCenter(find.byType(SubmenuButton)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + }); } List createTestMenus({ diff --git a/packages/flutter/test/material/navigation_rail_theme_test.dart b/packages/flutter/test/material/navigation_rail_theme_test.dart index 7a56586ca264..56265abbe85b 100644 --- a/packages/flutter/test/material/navigation_rail_theme_test.dart +++ b/packages/flutter/test/material/navigation_rail_theme_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { test('copyWith, ==, hashCode basics', () { @@ -145,7 +146,10 @@ void main() { expect(_indicatorDecoration(tester)?.shape, indicatorShape); }); - testWidgets('NavigationRail values take priority over NavigationRailThemeData values when both properties are specified', (WidgetTester tester) async { + testWidgets('NavigationRail values take priority over NavigationRailThemeData values when both properties are specified', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { const Color backgroundColor = Color(0x00000001); const double elevation = 7.0; const double selectedIconSize = 25.0; diff --git a/packages/flutter/test/material/paginated_data_table_test.dart b/packages/flutter/test/material/paginated_data_table_test.dart index d0f67a246beb..666843048eba 100644 --- a/packages/flutter/test/material/paginated_data_table_test.dart +++ b/packages/flutter/test/material/paginated_data_table_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'data_table_test_utils.dart'; @@ -70,7 +71,10 @@ void main() { setUp(() => source = TestDataSource()); tearDown(() => source.dispose()); - testWidgets('PaginatedDataTable paging', (WidgetTester tester) async { + testWidgets('PaginatedDataTable paging', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { final List log = []; await tester.pumpWidget(MaterialApp( @@ -379,7 +383,10 @@ void main() { expect(find.byWidgetPredicate((Widget widget) => widget is SizedBox && widget.height == (rowsPerPage - (rowCount % rowsPerPage)) * 46.0), findsOneWidget); }); - testWidgets('PaginatedDataTable control test', (WidgetTester tester) async { + testWidgets('PaginatedDataTable control test', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { TestDataSource source = TestDataSource() ..generation = 42; addTearDown(source.dispose); diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart index 5595fe6448fc..1cf37df7f314 100644 --- a/packages/flutter/test/material/reorderable_list_test.dart +++ b/packages/flutter/test/material/reorderable_list_test.dart @@ -11,6 +11,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { group('$ReorderableListView', () { @@ -689,7 +690,10 @@ void main() { handle.dispose(); }); - testWidgets("Doesn't hide accessibility when a child declares its own semantics", (WidgetTester tester) async { + testWidgets("Doesn't hide accessibility when a child declares its own semantics", + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); final Widget reorderableListView = ReorderableListView( onReorder: (int oldIndex, int newIndex) { }, diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 114fa002c9a6..9ce8f3f5770a 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2867,7 +2867,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('ScaffoldMessenger showSnackBar default animatiom', (WidgetTester tester) async { + testWidgets('ScaffoldMessenger showSnackBar default animation', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Scaffold( body: Builder( diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart index d764d74ac02c..c4a2f2bcd066 100644 --- a/packages/flutter/test/material/segmented_button_test.dart +++ b/packages/flutter/test/material/segmented_button_test.dart @@ -9,6 +9,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; @@ -21,6 +22,10 @@ Widget boilerplate({required Widget child}) { } void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } + testWidgets('SegmentsButton when compositing does not crash', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/135747 // If the render object holds on to a stale canvas reference, this will @@ -584,10 +589,6 @@ void main() { ), ); - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } - final Material material = tester.widget(find.descendant( of: find.byType(TextButton), matching: find.byType(Material), @@ -601,13 +602,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.08))); expect(material.textStyle?.color, theme.colorScheme.onSurface); // Highlighted (pressed). await gesture.down(center); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); + expect(getOverlayColor(tester), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.1))); expect(material.textStyle?.color, theme.colorScheme.onSurface); }); @@ -763,16 +764,13 @@ void main() { ); // Test foreground color is applied to the overlay color. - RenderObject overlayColor() { - return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); - } final TestGesture gesture = await tester.createGesture( kind: PointerDeviceKind.mouse, ); await gesture.addPointer(); await gesture.down(tester.getCenter(find.text('1'))); await tester.pumpAndSettle(); - expect(overlayColor(), paints..rect(color: foregroundColor.withOpacity(0.08))); + expect(getOverlayColor(tester), paints..rect(color: foregroundColor.withOpacity(0.08))); }); testWidgets('Disabled SegmentedButton has correct states when rebuilding', (WidgetTester tester) async { @@ -909,6 +907,144 @@ void main() { // The width of the SegmentedButton must be less than the width of the parent widget. expect(segmentedButtonWidth, lessThan(screenWidth)); }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/145527 + + testWidgets('SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option 1'), + ), + ButtonSegment( + value: 1, + label: Text('Option 2'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {1}, + ), + ), + ), + ), + ); + + // Hovered selected segment, + Offset center = tester.getCenter(find.text('Option 1')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Hovered unselected segment, + center = tester.getCenter(find.text('Option 2')); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted unselected segment (pressed). + center = tester.getCenter(find.text('Option 1')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Highlighted selected segment (pressed) + center = tester.getCenter(find.text('Option 2')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused unselected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + + // Focused selected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); + + testWidgets('SegmentedButton.styleFrom with transparent overlayColor', (WidgetTester tester) async { + const Color overlayColor = Colors.transparent; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SegmentedButton( + style: IconButton.styleFrom(overlayColor: overlayColor), + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {0}, + ), + ), + ), + ), + ); + + // Hovered, + final Offset center = tester.getCenter(find.text('Option')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor) + ..rect(color: overlayColor), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor)); + }); } Set enabled = const {}; diff --git a/packages/flutter/test/material/segmented_button_theme_test.dart b/packages/flutter/test/material/segmented_button_theme_test.dart index f978b4bb4a76..74c5062bf0de 100644 --- a/packages/flutter/test/material/segmented_button_theme_test.dart +++ b/packages/flutter/test/material/segmented_button_theme_test.dart @@ -3,10 +3,15 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + RenderObject getOverlayColor(WidgetTester tester) { + return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + } test('SegmentedButtonThemeData copyWith, ==, hashCode basics', () { expect(const SegmentedButtonThemeData(), const SegmentedButtonThemeData().copyWith()); @@ -476,4 +481,93 @@ void main() { expect(selectedIcon, findsNothing); } }); + + testWidgets('SegmentedButtonTheme SegmentedButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async { + const Color overlayColor = Color(0xffff0000); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + segmentedButtonTheme: SegmentedButtonThemeData( + style: SegmentedButton.styleFrom(overlayColor: overlayColor), + ), + ), + home: Scaffold( + body: Center( + child: SegmentedButton( + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Option 1'), + ), + ButtonSegment( + value: 1, + label: Text('Option 2'), + ), + ], + onSelectionChanged: (Set selected) {}, + selected: const {1}, + ), + ), + ), + ), + ); + + // Hovered selected segment, + Offset center = tester.getCenter(find.text('Option 1')); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Hovered unselected segment, + center = tester.getCenter(find.text('Option 2')); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08))); + + // Highlighted unselected segment (pressed). + center = tester.getCenter(find.text('Option 1')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Highlighted selected segment (pressed) + center = tester.getCenter(find.text('Option 2')); + await gesture.down(center); + await tester.pumpAndSettle(); + expect( + getOverlayColor(tester), + paints + ..rect(color: overlayColor.withOpacity(0.08)) + ..rect(color: overlayColor.withOpacity(0.1)), + ); + // Remove pressed and hovered states, + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused unselected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + + // Focused selected segment. + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1))); + }); } diff --git a/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart b/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart index 66c668f671b0..eafa7f828312 100644 --- a/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart +++ b/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart @@ -17,24 +17,15 @@ void main() { /// Builds test button items for each of the suggestions provided. List buildSuggestionButtons(List suggestions) { - final List buttonItems = []; - - for (final String suggestion in suggestions) { - buttonItems.add(ContextMenuButtonItem( - onPressed: () {}, - label: suggestion, - )); - } - - final ContextMenuButtonItem deleteButton = + return [ + for (final String suggestion in suggestions) + ContextMenuButtonItem(onPressed: () {}, label: suggestion), ContextMenuButtonItem( onPressed: () {}, type: ContextMenuButtonType.delete, label: 'DELETE', - ); - buttonItems.add(deleteButton); - - return buttonItems; + ), + ]; } /// Finds the container of the [SpellCheckSuggestionsToolbar] so that diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index d7e6a653ae32..89f5a4f84a57 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -50,6 +49,7 @@ Widget buildFrame({ TextDirection textDirection = TextDirection.ltr, TabAlignment? tabAlignment, TabBarTheme? tabBarTheme, + Decoration? indicator, bool? useMaterial3, }) { if (secondaryTabBar) { @@ -88,6 +88,7 @@ Widget buildFrame({ indicatorColor: indicatorColor, padding: padding, tabAlignment: tabAlignment, + indicator: indicator, ), ), ); @@ -119,9 +120,6 @@ Widget buildLeftRightApp({required List tabs, required String value, boo } void main() { - // TODO(polina-c): dispose TabController, https://github.com/flutter/flutter/issues/144910 [leaks-to-clean] - LeakTesting.settings = LeakTesting.settings.withIgnoredAll(); - setUp(() { debugResetSemanticsIdCounter(); }); @@ -7075,4 +7073,50 @@ void main() { expect(find.text('View 0'), findsNothing); expect(find.text('View 2'), findsOneWidget); }); + + testWidgets('Tab indicator painter image configuration', (WidgetTester tester) async { + final List tabs = ['A', 'B']; + final TestIndicatorDecoration decoration = TestIndicatorDecoration(); + + Widget buildTabs({ + TextDirection textDirection = TextDirection.ltr, + double ratio = 1.0, + }) { + return MaterialApp( + home: MediaQuery( + data: MediaQueryData(devicePixelRatio: ratio), + child: Directionality( + textDirection: textDirection, + child: DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + indicator: decoration, + tabs: tabs.map((String tab) => Tab(text: tab)).toList(), + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildTabs()); + + ImageConfiguration config = decoration.painters.last.lastConfiguration!; + expect(config.size?.width, closeTo(14.1, 0.1)); + expect(config.size?.height, equals(48.0)); + expect(config.textDirection, TextDirection.ltr); + expect(config.devicePixelRatio, 1.0); + + await tester.pumpWidget(buildTabs(textDirection: TextDirection.rtl, ratio: 2.33)); + + config = decoration.painters.last.lastConfiguration!; + expect(config.size?.width, closeTo(14.1, 0.1)); + expect(config.size?.height, equals(48.0)); + expect(config.textDirection, TextDirection.rtl); + expect(config.devicePixelRatio, 2.33); + }); } diff --git a/packages/flutter/test/material/tabs_utils.dart b/packages/flutter/test/material/tabs_utils.dart index 62f6362c7cf8..dfb0233be1e4 100644 --- a/packages/flutter/test/material/tabs_utils.dart +++ b/packages/flutter/test/material/tabs_utils.dart @@ -228,3 +228,24 @@ class _TabAlwaysKeepAliveWidgetState extends State wit return Text(TabAlwaysKeepAliveWidget.text); } } + +// This decoration is used to test the indicator decoration image configuration. +class TestIndicatorDecoration extends Decoration { + final List painters = []; + + @override + BoxPainter createBoxPainter([VoidCallback? onChanged]) { + final TestIndicatorBoxPainter painter = TestIndicatorBoxPainter(); + painters.add(painter); + return painter; + } +} + +class TestIndicatorBoxPainter extends BoxPainter { + ImageConfiguration? lastConfiguration; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + lastConfiguration = configuration; + } +} diff --git a/packages/flutter/test/material/user_accounts_drawer_header_test.dart b/packages/flutter/test/material/user_accounts_drawer_header_test.dart index a106eaabf8fb..45d511af2ec4 100644 --- a/packages/flutter/test/material/user_accounts_drawer_header_test.dart +++ b/packages/flutter/test/material/user_accounts_drawer_header_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/semantics_tester.dart'; const Key avatarA = Key('A'); @@ -261,7 +262,10 @@ void main() { expect(transformWidget.transform.getRotation()[4], 1.0); }); - testWidgets('UserAccountsDrawerHeader icon color changes', (WidgetTester tester) async { + testWidgets('UserAccountsDrawerHeader icon color changes', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Material( child: UserAccountsDrawerHeader( diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index 47f4f18f8708..73be30b406f7 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -1680,7 +1680,7 @@ void main() { ..layout(); expect(painter.height, 100); }); - }, skip: kIsWeb && !isSkiaWeb); // [intended] strut spport for HTML renderer https://github.com/flutter/flutter/issues/32243. + }, skip: kIsWeb && !isSkiaWeb); // [intended] strut support for HTML renderer https://github.com/flutter/flutter/issues/32243. test('getOffsetForCaret does not crash on decomposed characters', () { final TextPainter painter = TextPainter( diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart index 25a11b7d55aa..015e72465e04 100644 --- a/packages/flutter/test/rendering/box_test.dart +++ b/packages/flutter/test/rendering/box_test.dart @@ -503,6 +503,23 @@ void main() { // At least 2 lines. expect(constrainedHeight, greaterThanOrEqualTo(2 * unconstrainedHeight)); }); + + test('paints even when its size is empty', () { + // Regression test for https://github.com/flutter/flutter/issues/146840. + final RenderParagraph child = RenderParagraph( + const TextSpan(text: ''), + textDirection: TextDirection.ltr, + ); + final RenderConstraintsTransformBox box = RenderConstraintsTransformBox( + alignment: Alignment.center, + textDirection: TextDirection.ltr, + constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity), + child: child, + ); + + layout(box, constraints: BoxConstraints.tight(Size.zero), phase: EnginePhase.paint); + expect(box, paints..paragraph()); + }); }); test ('getMinIntrinsicWidth error handling', () { diff --git a/packages/flutter/test/rendering/cached_intrinsics_test.dart b/packages/flutter/test/rendering/cached_intrinsics_test.dart index 1090820ddbd5..25c99e95f7a9 100644 --- a/packages/flutter/test/rendering/cached_intrinsics_test.dart +++ b/packages/flutter/test/rendering/cached_intrinsics_test.dart @@ -222,7 +222,7 @@ void main() { ); }); - test('Cactches inconsistencies between computeDryBaseline and computeDistanceToActualBaseline', () { + test('Catches inconsistencies between computeDryBaseline and computeDistanceToActualBaseline', () { final RenderDryBaselineTestBox test = RenderDryBaselineTestBox(); layout(test, phase: EnginePhase.composite); diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart index 0cb606dc3526..9dd722d073bf 100644 --- a/packages/flutter/test/rendering/flex_test.dart +++ b/packages/flutter/test/rendering/flex_test.dart @@ -674,7 +674,7 @@ void main() { test('cross axis intrinsics, with ascending flex flow layout', () { const BoxConstraints square = BoxConstraints.tightFor(width: 5.0, height: 5.0); - // 3 'A's separated by zero-width spaces. Max instrinsic width = 30, min intrinsic width = 10 + // 3 'A's separated by zero-width spaces. Max intrinsic width = 30, min intrinsic width = 10 final TextSpan textSpan = TextSpan(text: List.filled(3, 'A').join('\u200B') , style: const TextStyle(fontSize: 10)); final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square); final RenderParagraph box2 = RenderParagraph(textSpan, textDirection: TextDirection.ltr); @@ -711,7 +711,7 @@ void main() { test('cross axis intrinsics, with descending flex flow layout', () { const BoxConstraints square = BoxConstraints.tightFor(width: 5.0, height: 5.0); - // 3 'A's separated by zero-width spaces. Max instrinsic width = 30, min intrinsic width = 10 + // 3 'A's separated by zero-width spaces. Max intrinsic width = 30, min intrinsic width = 10 final TextSpan textSpan = TextSpan(text: List.filled(3, 'A').join('\u200B') , style: const TextStyle(fontSize: 10)); final RenderConstrainedBox box1 = RenderConstrainedBox(additionalConstraints: square); final RenderParagraph box2 = RenderParagraph(textSpan, textDirection: TextDirection.ltr); diff --git a/packages/flutter/test/rendering/painting_mocks_test.dart b/packages/flutter/test/rendering/painting_mocks_test.dart new file mode 100644 index 000000000000..c83aa6876030 --- /dev/null +++ b/packages/flutter/test/rendering/painting_mocks_test.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 'dart:ui' as ui; + +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_test/flutter_test.dart'; + +final List log = []; + +void main() { + final PaintingMocksTestRenderingFlutterBinding binding = PaintingMocksTestRenderingFlutterBinding.ensureInitialized(); + + test('createSceneBuilder et al', () async { + final RenderView root = RenderView( + view: binding.platformDispatcher.views.single, + configuration: const ViewConfiguration(), + ); + root.attach(PipelineOwner()); + root.prepareInitialFrame(); + expect(log, isEmpty); + root.compositeFrame(); + expect(log, ['createSceneBuilder']); + log.clear(); + final PaintingContext context = PaintingContext(ContainerLayer(), Rect.zero); + expect(log, isEmpty); + context.canvas; + expect(log, ['createPictureRecorder', 'createCanvas']); + log.clear(); + context.addLayer(ContainerLayer()); + expect(log, isEmpty); + context.canvas; + expect(log, ['createPictureRecorder', 'createCanvas']); + log.clear(); + }); +} + + +class PaintingMocksTestRenderingFlutterBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding { + @override + void initInstances() { + super.initInstances(); + _instance = this; + } + + static PaintingMocksTestRenderingFlutterBinding get instance => BindingBase.checkInstance(_instance); + static PaintingMocksTestRenderingFlutterBinding? _instance; + + static PaintingMocksTestRenderingFlutterBinding ensureInitialized() { + if (PaintingMocksTestRenderingFlutterBinding._instance == null) { + PaintingMocksTestRenderingFlutterBinding(); + } + return PaintingMocksTestRenderingFlutterBinding.instance; + } + + @override + ui.SceneBuilder createSceneBuilder() { + log.add('createSceneBuilder'); + return super.createSceneBuilder(); + } + + @override + ui.PictureRecorder createPictureRecorder() { + log.add('createPictureRecorder'); + return super.createPictureRecorder(); + } + + @override + Canvas createCanvas(ui.PictureRecorder recorder) { + log.add('createCanvas'); + return super.createCanvas(recorder); + } +} diff --git a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart index f74f026bcb0a..ceeb6b1e0748 100644 --- a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart +++ b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart @@ -111,5 +111,5 @@ void main() { paragraph.strutStyle = const StrutStyle(fontSize: 100, forceStrutHeight: true); expect(paragraph.getMaxIntrinsicHeight(double.infinity), 100); - }, skip: kIsWeb && !isSkiaWeb); // [intended] strut spport for HTML renderer https://github.com/flutter/flutter/issues/32243. + }, skip: kIsWeb && !isSkiaWeb); // [intended] strut support for HTML renderer https://github.com/flutter/flutter/issues/32243. } diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index 90b2622f301c..0432b8530c61 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'semantics_tester.dart'; @@ -2541,10 +2540,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/6128. - testWidgets('Draggable plays nice with onTap', - // TODO(polina-c): fix the leaking ImmediateMultiDragGestureRecognizer https://github.com/flutter/flutter/pull/144396 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), - (WidgetTester tester) async { + testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async { late final OverlayEntry entry; addTearDown(() => entry..remove()..dispose()); @@ -2578,6 +2574,7 @@ void main() { await firstGesture.moveBy(const Offset(100.0, 0.0)); await secondGesture.up(); + await firstGesture.up(); }); testWidgets('DragTarget does not set state when remove from the tree', (WidgetTester tester) async { @@ -3104,6 +3101,92 @@ void main() { ); }); + testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async { + final GlobalKey rootNavigatorKey = GlobalKey(); + final GlobalKey childNavigatorKey = GlobalKey(); + // Create a [MaterialApp], with a nested [Navigator], which has the + // [Draggable]. + await tester.pumpWidget(MaterialApp( + navigatorKey: rootNavigatorKey, + home: Column( + children: [ + SizedBox( + height: 200.0, + child: Navigator( + key: childNavigatorKey, + onGenerateRoute: (RouteSettings settings) { + if (settings.name == '/') { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) => const LongPressDraggable( + data: 1, + feedback: Text('Dragging'), + rootOverlay: true, + child: Text('Source'), + ), + ); + } + throw UnsupportedError('Unsupported route: $settings'); + }, + ), + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return const SizedBox( + height: 300.0, child: Center(child: Text('Target 1')), + ); + }, + ), + ], + ), + )); + + final Offset firstLocation = tester.getCenter(find.text('Source')); + final TestGesture gesture = + await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(kLongPressTimeout); + + final Offset secondLocation = tester.getCenter(find.text('Target 1')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + // Expect that the feedback widget is a descendant of the root overlay, + // but not a descendant of the child overlay. + expect( + find.descendant( + of: find.byType(Overlay).first, + matching: find.text('Dragging'), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byType(Overlay).last, + matching: find.text('Dragging'), + ), + findsNothing, + ); + }); + + testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async { + const HitTestBehavior hitTestBehavior = HitTestBehavior.opaque; + + await tester.pumpWidget( + const MaterialApp( + home: Column( + children: [ + LongPressDraggable( + hitTestBehavior: hitTestBehavior, + feedback: SizedBox(), + child: SizedBox(), + ), + ], + ), + ), + ); + expect(tester.widget(find.descendant(of: find.byType(Column), matching: find.byType(Listener))).behavior, hitTestBehavior); + }); + // Regression test for https://github.com/flutter/flutter/issues/72483 testWidgets('Drag and drop - DragTarget can accept Draggable data', (WidgetTester tester) async { final List accepted = []; @@ -3434,10 +3517,7 @@ void main() { }); // Regression test for https://github.com/flutter/flutter/issues/92083 - testWidgets('feedback respect the MouseRegion cursor configure', - // TODO(polina-c): fix the leaking ImmediateMultiDragGestureRecognizer https://github.com/flutter/flutter/pull/144396 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), - (WidgetTester tester) async { + testWidgets('feedback respect the MouseRegion cursor configure', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Column( @@ -3463,6 +3543,7 @@ void main() { await tester.pump(); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grabbing); + gesture.up(); }); testWidgets('configurable feedback ignore pointer behavior', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index fa31bbadd713..262d0a720896 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -1126,4 +1126,181 @@ void main() { await tester.pump(); expect(find.text('test_error'), findsNothing); }); + + testWidgets('AutovalidateMode.onUnfocus', (WidgetTester tester) async { + final GlobalKey formKey = GlobalKey(); + String? errorText(String? value) => '$value/error'; + + Widget builder() { + return MaterialApp( + theme: ThemeData(), + home: MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUnfocus, + child: Material( + child: Column( + children: [ + TextFormField( + initialValue: 'bar', + validator: errorText, + ), + TextFormField( + initialValue: 'bar', + validator: errorText, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + // No error text is visible yet. + expect(find.text(errorText('foo')!), findsNothing); + + // Enter text in the first TextFormField. + await tester.enterText(find.byType(TextFormField).first, 'foo'); + await tester.pumpAndSettle(); + + // No error text is visible yet. + expect(find.text(errorText('foo')!), findsNothing); + + // Tap on the second TextFormField to trigger validation. + // This should trigger validation for the first TextFormField as well. + await tester.tap(find.byType(TextFormField).last); + await tester.pumpAndSettle(); + + // Verify that the error text is displayed for the first TextFormField. + expect(find.text(errorText('foo')!), findsOneWidget); + expect(find.text(errorText('bar')!), findsNothing); + + // Tap on the first TextFormField to trigger validation. + await tester.tap(find.byType(TextFormField).first); + await tester.pumpAndSettle(); + + // Verify that the both error texts are displayed. + expect(find.text(errorText('foo')!), findsOneWidget); + expect(find.text(errorText('bar')!), findsOneWidget); + }); + + testWidgets('Validate conflicting AutovalidateModes', (WidgetTester tester) async { + final GlobalKey formKey = GlobalKey(); + String? errorText(String? value) => '$value/error'; + + Widget builder() { + return MaterialApp( + theme: ThemeData(), + home: MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUnfocus, + child: Material( + child: Column( + children: [ + TextFormField( + autovalidateMode: AutovalidateMode.always, + initialValue: 'foo', + validator: errorText, + ), + TextFormField( + autovalidateMode: AutovalidateMode.disabled, + initialValue: 'bar', + validator: errorText, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + // Verify that the error text is displayed for the first TextFormField. + expect(find.text(errorText('foo')!), findsOneWidget); + + // Enter text in the TextFormField. + await tester.enterText(find.byType(TextFormField).first, 'foo'); + await tester.pumpAndSettle(); + + // Click in the second TextFormField to trigger validation. + await tester.tap(find.byType(TextFormField).last); + await tester.pumpAndSettle(); + + // No error text is visible yet for the second TextFormField. + expect(find.text(errorText('bar')!), findsNothing); + + // Now click in the first TextFormField to trigger validation for the second TextFormField. + await tester.tap(find.byType(TextFormField).first); + await tester.pumpAndSettle(); + + // Verify that the error text is displayed for the second TextFormField. + expect(find.text(errorText('bar')!), findsOneWidget); + }); + + testWidgets('FocusNode should move to next field when TextInputAction.next is received', (WidgetTester tester) async { + final GlobalKey formKey = GlobalKey(); + final FocusNode focusNode1 = FocusNode(); + addTearDown(focusNode1.dispose); + final FocusNode focusNode2 = FocusNode(); + addTearDown(focusNode2.dispose); + final TextEditingController controller1 = TextEditingController(); + addTearDown(controller1.dispose); + final TextEditingController controller2 = TextEditingController(); + addTearDown(controller2.dispose); + + final Widget widget = MaterialApp( + home: MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Material( + child: Form( + key: formKey, + child: Column( + children: [ + TextFormField( + focusNode: focusNode1, + controller: controller1, + textInputAction: TextInputAction.next, + ), + TextFormField( + focusNode: focusNode2, + controller: controller2, + ), + ], + ), + ), + ), + ), + ), + ), + ); + + await tester.pumpWidget(widget); + + await tester.showKeyboard(find.byType(TextFormField).first); + await tester.testTextInput.receiveAction(TextInputAction.next); + await tester.pumpAndSettle(); + + expect(focusNode2.hasFocus, isTrue); + }); } diff --git a/packages/flutter/test/widgets/heroes_test.dart b/packages/flutter/test/widgets/heroes_test.dart index d9bd88e1fc01..9076c843b015 100644 --- a/packages/flutter/test/widgets/heroes_test.dart +++ b/packages/flutter/test/widgets/heroes_test.dart @@ -301,7 +301,10 @@ Future main() async { expect(find.byKey(thirdKey), isInCard); }); - testWidgets('Heroes still animate after hero controller is swapped.', (WidgetTester tester) async { + testWidgets('Heroes still animate after hero controller is swapped.', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final UniqueKey heroKey = UniqueKey(); final HeroController controller1 = HeroController(); diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart index af15f5c9ad2d..033d60351bdc 100644 --- a/packages/flutter/test/widgets/image_test.dart +++ b/packages/flutter/test/widgets/image_test.dart @@ -824,7 +824,7 @@ void main() { }); testWidgets('Precache removes original listener immediately after future completes, does not crash on successive calls #25143', - // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787 [leaks-to-clean] + // TODO(polina-c): dispose ImageStreamCompleterHandle, https://github.com/flutter/flutter/issues/145599 [leaks-to-clean] experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), (WidgetTester tester) async { final _TestImageStreamCompleter imageStreamCompleter = _TestImageStreamCompleter(); diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 0bb9ba9d5d73..3407f61dcf56 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -2989,7 +2989,7 @@ void main() { const List> myPages = >[ MaterialPage(child: Text('page1')), MaterialPage( - child: PopScope( + child: PopScope( canPop: false, child: Text('page2'), ), @@ -4908,9 +4908,9 @@ void main() { home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { builderSetState = setState; - return PopScope( + return PopScope( canPop: canPop(), - onPopInvoked: (bool success) { + onPopInvokedWithResult: (bool success, Object? result) { if (success || pages.last == _Page.noPop) { return; } @@ -5024,9 +5024,9 @@ void main() { MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return PopScope( + return PopScope( canPop: canPop(), - onPopInvoked: (bool success) { + onPopInvokedWithResult: (bool success, Object? result) { if (success || pages.last == _Page.noPop) { return; } @@ -5117,9 +5117,9 @@ void main() { MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return PopScope( + return PopScope( canPop: canPop(), - onPopInvoked: (bool success) { + onPopInvokedWithResult: (bool success, Object? result) { if (success || pages.last == _PageWithYesPop.noPop) { return; } @@ -5189,7 +5189,7 @@ void main() { child: _LinksPage( title: 'Can pop page', canPop: true, - onPopInvoked: (bool didPop) { + onPopInvoked: (bool didPop, void result) { onPopInvokedCallCount += 1; }, ), @@ -5556,7 +5556,7 @@ class _LinksPage extends StatelessWidget { final bool? canPop; final VoidCallback? onBack; final String title; - final PopInvokedCallback? onPopInvoked; + final PopInvokedWithResultCallback? onPopInvoked; @override Widget build(BuildContext context) { @@ -5575,9 +5575,9 @@ class _LinksPage extends StatelessWidget { child: const Text('Go back'), ), if (canPop != null) - PopScope( + PopScope( canPop: canPop!, - onPopInvoked: onPopInvoked, + onPopInvokedWithResult: onPopInvoked, child: const SizedBox.shrink(), ), ], diff --git a/packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart b/packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart index af368873925c..34a7f3ea4596 100644 --- a/packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart +++ b/packages/flutter/test/widgets/overscroll_stretch_indicator_test.dart @@ -1177,4 +1177,69 @@ void main() { expect(tester.layers, contains(isA())); }); + + testWidgets('Stretching animation completes after fling under scroll physics with high friction', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/146277 + final GlobalKey box1Key = GlobalKey(); + final GlobalKey box2Key = GlobalKey(); + final GlobalKey box3Key = GlobalKey(); + late final OverscrollNotification overscrollNotification; + final ScrollController controller = ScrollController(); + addTearDown(controller.dispose); + + await tester.pumpWidget(NotificationListener( + child: buildTest( + box1Key, + box2Key, + box3Key, + controller, + physics: const _HighFrictionClampingScrollPhysics(), + ), + onNotification: (OverscrollNotification notification) { + overscrollNotification = notification; + return false; + }, + )); + + expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); + expect(find.byType(GlowingOverscrollIndicator), findsNothing); + final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); + final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); + final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); + + expect(controller.offset, 0.0); + expect(box1.localToGlobal(Offset.zero), Offset.zero); + expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); + expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); + + // We fling to the trailing edge and let it settle. + await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0); + await tester.pumpAndSettle(); + + // We are now at the trailing edge + expect(overscrollNotification.velocity, lessThan(25)); + expect(controller.offset, 150.0); + expect(box1.localToGlobal(Offset.zero).dy, -150.0); + expect(box2.localToGlobal(Offset.zero).dy, 100.0); + expect(box3.localToGlobal(Offset.zero).dy, 350.0); + }); +} + +final class _HighFrictionClampingScrollPhysics extends ScrollPhysics { + const _HighFrictionClampingScrollPhysics({super.parent}); + + @override + ScrollPhysics applyTo(ScrollPhysics? ancestor) { + return _HighFrictionClampingScrollPhysics(parent: buildParent(ancestor)); + } + + @override + Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { + return ClampingScrollSimulation( + position: position.pixels, + velocity: velocity, + friction: 0.94, + tolerance: tolerance, + ); + } } diff --git a/packages/flutter/test/widgets/pop_scope_test.dart b/packages/flutter/test/widgets/pop_scope_test.dart index 116951ce7877..8e6d1040533e 100644 --- a/packages/flutter/test/widgets/pop_scope_test.dart +++ b/packages/flutter/test/widgets/pop_scope_test.dart @@ -47,7 +47,7 @@ void main() { builder: (BuildContext buildContext, StateSetter stateSetter) { context = buildContext; setState = stateSetter; - return PopScope( + return PopScope( canPop: canPop, child: const Center( child: Column( @@ -79,6 +79,94 @@ void main() { variant: TargetPlatformVariant.all(), ); + testWidgets('pop scope can receive result', (WidgetTester tester) async { + Object? receivedResult; + final Object poppedResult = Object(); + final GlobalKey nav = GlobalKey(); + await tester.pumpWidget( + MaterialApp( + initialRoute: '/', + navigatorKey: nav, + home: Scaffold( + body: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + receivedResult = result; + }, + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Home/PopScope Page'), + ], + ), + ), + ), + ), + ), + ); + + nav.currentState!.maybePop(poppedResult); + await tester.pumpAndSettle(); + expect(receivedResult, poppedResult); + }, + variant: TargetPlatformVariant.all(), + ); + + testWidgets('pop scope can have Object? generic type while route has stricter generic type', (WidgetTester tester) async { + Object? receivedResult; + const int poppedResult = 13; + final GlobalKey nav = GlobalKey(); + await tester.pumpWidget( + MaterialApp( + initialRoute: '/', + navigatorKey: nav, + home: Scaffold( + body: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + receivedResult = result; + }, + child: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Home/PopScope Page'), + ], + ), + ), + ), + ), + ), + ); + + nav.currentState!.push( + MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + body: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + receivedResult = result; + }, + child: const Center( + child: Text('new page'), + ), + ), + ); + }, + ), + ); + await tester.pumpAndSettle(); + expect(find.text('new page'), findsOneWidget); + + nav.currentState!.maybePop(poppedResult); + await tester.pumpAndSettle(); + expect(receivedResult, poppedResult); + }, + variant: TargetPlatformVariant.all(), + ); + testWidgets('toggling canPop on secondary route allows/prevents backs', (WidgetTester tester) async { final GlobalKey nav = GlobalKey(); bool canPop = true; @@ -115,9 +203,9 @@ void main() { builder: (BuildContext context, StateSetter stateSetter) { oneContext = context; setState = stateSetter; - return PopScope( + return PopScope( canPop: canPop, - onPopInvoked: (bool didPop) { + onPopInvokedWithResult: (bool didPop, Object? result) { lastPopSuccess = didPop; }, child: const Center( @@ -271,7 +359,7 @@ void main() { if (!usePopScope) { return child; } - return const PopScope( + return const PopScope( canPop: false, child: child, ); @@ -314,12 +402,12 @@ void main() { return Column( children: [ if (usePopScope1) - const PopScope( + const PopScope( canPop: false, child: Text('hello'), ), if (usePopScope2) - const PopScope( + const PopScope( canPop: false, child: Text('hello'), ), diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart index 49508884e74d..3c8cd459b1d9 100644 --- a/packages/flutter/test/widgets/routes_test.dart +++ b/packages/flutter/test/widgets/routes_test.dart @@ -1736,7 +1736,7 @@ void main() { semantics.dispose(); }, variant: const TargetPlatformVariant({TargetPlatform.iOS})); - testWidgets('focus traverse correct when pop multiple page simultaneously', (WidgetTester tester) async { + testWidgets('focus traversal is correct when popping multiple pages simultaneously', (WidgetTester tester) async { // Regression test: https://github.com/flutter/flutter/issues/48903 final GlobalKey navigatorKey = GlobalKey(); await tester.pumpWidget(MaterialApp( diff --git a/packages/flutter/test/widgets/semantics_tester_generate_test_semantics_expression_for_current_semantics_tree_test.dart b/packages/flutter/test/widgets/semantics_tester_generate_test_semantics_expression_for_current_semantics_tree_test.dart index 1f3267983bff..1f7aec466cf9 100644 --- a/packages/flutter/test/widgets/semantics_tester_generate_test_semantics_expression_for_current_semantics_tree_test.dart +++ b/packages/flutter/test/widgets/semantics_tester_generate_test_semantics_expression_for_current_semantics_tree_test.dart @@ -10,7 +10,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import 'semantics_tester.dart'; @@ -53,10 +52,7 @@ void _tests() { // also update this code to reflect the new output. // // This test is flexible w.r.t. leading and trailing whitespace. - testWidgets('generates code', - // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), - (WidgetTester tester) async { + testWidgets('generates code', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await pumpTestWidget(tester); final String code = semantics diff --git a/packages/flutter/test/widgets/shape_decoration_test.dart b/packages/flutter/test/widgets/shape_decoration_test.dart index 605ae1f74b7c..b0e7c67bd541 100644 --- a/packages/flutter/test/widgets/shape_decoration_test.dart +++ b/packages/flutter/test/widgets/shape_decoration_test.dart @@ -19,7 +19,7 @@ Future main() async { final ImageProvider image = TestImageProvider(0, 0, image: rawImage); testWidgets('ShapeDecoration.image', - // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787 [leaks-to-clean] + // TODO(polina-c): dispose ImageStreamCompleterHandle, https://github.com/flutter/flutter/issues/145599 [leaks-to-clean] experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart index 63868ff77fd7..4eda82dcac6d 100644 --- a/packages/flutter/test/widgets/slivers_test.dart +++ b/packages/flutter/test/widgets/slivers_test.dart @@ -338,14 +338,10 @@ void main() { testWidgets('SliverFixedExtentList handles underflow when its children changes', (WidgetTester tester) async { final List items = ['1', '2', '3', '4', '5', '6']; final List initializedChild = []; - List children = []; - for (final String item in items) { - children.add( - StateInitSpy( - item, () => initializedChild.add(item), key: ValueKey(item), - ), - ); - } + List children = [ + for (final String item in items) + StateInitSpy(item, () => initializedChild.add(item), key: ValueKey(item)), + ]; final ScrollController controller = ScrollController(initialScrollOffset: 5400); addTearDown(controller.dispose); diff --git a/packages/flutter/test/widgets/transitions_test.dart b/packages/flutter/test/widgets/transitions_test.dart index f1b39b509658..14f6109b6a88 100644 --- a/packages/flutter/test/widgets/transitions_test.dart +++ b/packages/flutter/test/widgets/transitions_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { testWidgets('toString control test', (WidgetTester tester) async { @@ -95,12 +96,17 @@ void main() { expect(actualDecoration.boxShadow, null); }); - testWidgets('animations work with curves test', (WidgetTester tester) async { - final Animation curvedDecorationAnimation = - decorationTween.animate(CurvedAnimation( + testWidgets('animations work with curves test', + // TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in] + experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const ['CurvedAnimation']), + (WidgetTester tester) async { + final CurvedAnimation curvedAnimation = CurvedAnimation( parent: controller, curve: Curves.easeOut, - )); + ); + addTearDown(curvedAnimation.dispose); + final Animation curvedDecorationAnimation = + decorationTween.animate(curvedAnimation); final DecoratedBoxTransition transitionUnderTest = DecoratedBoxTransition( decoration: curvedDecorationAnimation, diff --git a/packages/flutter/test/widgets/view_test.dart b/packages/flutter/test/widgets/view_test.dart index 587045422a53..64221196bb6f 100644 --- a/packages/flutter/test/widgets/view_test.dart +++ b/packages/flutter/test/widgets/view_test.dart @@ -346,8 +346,7 @@ void main() { }); testWidgets('correctly switches between view configurations', - // TODO(polina-c): clean up leaks, https://github.com/flutter/flutter/issues/134787 [leaks-to-clean] - experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), + experimentalLeakTesting: LeakTesting.settings.withIgnoredAll(), // Leaking by design as contains deprecated items. (WidgetTester tester) async { await tester.pumpWidget( wrapWithView: false, diff --git a/packages/flutter/test_private/pubspec.yaml b/packages/flutter/test_private/pubspec.yaml index f539d322d9e0..6ae2d230a782 100644 --- a/packages/flutter/test_private/pubspec.yaml +++ b/packages/flutter/test_private/pubspec.yaml @@ -9,7 +9,7 @@ dependencies: # # For detailed instructions, refer to: # https://github.com/flutter/flutter/wiki/Updating-dependencies-in-Flutter - meta: 1.12.0 + meta: 1.14.0 path: 1.9.0 process: 5.0.2 process_runner: 4.2.0 @@ -20,4 +20,4 @@ dependencies: file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: d7b1 +# PUBSPEC CHECKSUM: d6b3 diff --git a/packages/flutter/test_private/test/pubspec.yaml b/packages/flutter/test_private/test/pubspec.yaml index 0c67a7ac750e..1563e8940d0d 100644 --- a/packages/flutter/test_private/test/pubspec.yaml +++ b/packages/flutter/test_private/test/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: sdk: flutter characters: 1.3.0 collection: 1.18.0 - meta: 1.12.0 + meta: 1.14.0 vector_math: 2.1.4 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,8 +33,8 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_goldens: @@ -47,4 +47,4 @@ dev_dependencies: process: 5.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: c1b6 +# PUBSPEC CHECKSUM: 6fba diff --git a/packages/flutter_driver/lib/fix_data/README.md b/packages/flutter_driver/lib/fix_data/README.md index 1060b4de6ea1..3a3622d63065 100644 --- a/packages/flutter_driver/lib/fix_data/README.md +++ b/packages/flutter_driver/lib/fix_data/README.md @@ -38,7 +38,7 @@ format do not break Flutter. See [tools/bots/flutter/analyze_flutter_flutter.sh](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_flutter.sh) for where the flutter fix tests are invoked for the dart repo. -See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) +See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/main/dev/bots/test.dart) for where the flutter fix tests are invoked for the flutter/flutter repo. When possible, please coordinate changes to this directory that might affect the diff --git a/packages/flutter_driver/lib/src/driver/profiling_summarizer.dart b/packages/flutter_driver/lib/src/driver/profiling_summarizer.dart index e70e86679756..09f970ee51b8 100644 --- a/packages/flutter_driver/lib/src/driver/profiling_summarizer.dart +++ b/packages/flutter_driver/lib/src/driver/profiling_summarizer.dart @@ -16,7 +16,7 @@ const Set kProfilingEvents = { }; // These field names need to be in-sync with: -// https://github.com/flutter/engine/blob/master/shell/profiling/sampling_profiler.cc +// https://github.com/flutter/engine/blob/main/shell/profiling/sampling_profiler.cc const String _kCpuProfile = 'CpuUsage'; const String _kGpuProfile = 'GpuUsage'; const String _kMemoryProfile = 'MemoryUsage'; diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index e19a2e79f422..f3931d7ea7a8 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -14,8 +14,8 @@ dependencies: fuchsia_remote_debug_protocol: sdk: flutter path: 1.9.0 - meta: 1.12.0 - vm_service: 14.2.0 + meta: 1.14.0 + vm_service: 14.2.1 webdriver: 3.0.3 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -36,12 +36,12 @@ dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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" dev_dependencies: fake_async: 1.3.1 - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -49,7 +49,7 @@ dev_dependencies: convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +67,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,4 +75,4 @@ dev_dependencies: webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: ca59 +# PUBSPEC CHECKSUM: 5860 diff --git a/packages/flutter_goldens/lib/flutter_goldens.dart b/packages/flutter_goldens/lib/flutter_goldens.dart index 63abf6ba18a3..ad5e5b282cef 100644 --- a/packages/flutter_goldens/lib/flutter_goldens.dart +++ b/packages/flutter_goldens/lib/flutter_goldens.dart @@ -22,7 +22,7 @@ export 'skia_client.dart'; // titled "Inconsequential golden test" in this file: // /packages/flutter/test/widgets/basic_test.dart -// TODO(ianh): sort the parameters and arguments in this file so they use a consistent order througout. +// TODO(ianh): sort the parameters and arguments in this file so they use a consistent order throughout. const String _kFlutterRootKey = 'FLUTTER_ROOT'; @@ -38,15 +38,43 @@ bool _isMainBranch(String? branch) { /// instantiated is based on the current testing environment. /// /// When set, the `namePrefix` is prepended to the names of all gold images. +/// +/// This function assumes the [goldenFileComparator] has been set to a +/// [LocalFileComparator], which happens in the bootstrap code used when running +/// tests using `flutter test`. This should not be called when running a test +/// using `flutter run`, as in that environment, the [goldenFileComparator] is a +/// [TrivialComparator]. Future testExecutable(FutureOr Function() testMain, {String? namePrefix}) async { + assert( + goldenFileComparator is LocalFileComparator, + 'The flutter_goldens package should be used from a flutter_test_config.dart ' + 'file, which is only invoked when using "flutter test". The "flutter test" ' + 'bootstrap logic sets "goldenFileComparator" to a LocalFileComparator. It ' + 'appears in this instance however that the "goldenFileComparator" is a ' + '${goldenFileComparator.runtimeType}.\n' + 'See also: https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html', + ); const Platform platform = LocalPlatform(); const FileSystem fs = LocalFileSystem(); if (FlutterPostSubmitFileComparator.isForEnvironment(platform)) { - goldenFileComparator = await FlutterPostSubmitFileComparator.fromDefaultComparator(platform, namePrefix: namePrefix, log: print, fs: fs); + goldenFileComparator = await FlutterPostSubmitFileComparator.fromLocalFileComparator( + localFileComparator: goldenFileComparator as LocalFileComparator, + platform, + namePrefix: namePrefix, + log: print, + fs: fs, + ); } else if (FlutterPreSubmitFileComparator.isForEnvironment(platform)) { - goldenFileComparator = await FlutterPreSubmitFileComparator.fromDefaultComparator(platform, namePrefix: namePrefix, log: print, fs: fs); + goldenFileComparator = await FlutterPreSubmitFileComparator.fromLocalFileComparator( + localFileComparator: goldenFileComparator as LocalFileComparator, + platform, + namePrefix: namePrefix, + log: print, + fs: fs, + ); } else if (FlutterSkippingFileComparator.isForEnvironment(platform)) { - goldenFileComparator = FlutterSkippingFileComparator.fromDefaultComparator( + goldenFileComparator = FlutterSkippingFileComparator.fromLocalFileComparator( + localFileComparator: goldenFileComparator as LocalFileComparator, 'Golden file testing is not executed on Cirrus, or LUCI environments ' 'outside of flutter/flutter, or in test shards that are not configured ' 'for using goldctl.', @@ -55,7 +83,12 @@ Future testExecutable(FutureOr Function() testMain, {String? namePre fs: fs, ); } else { - goldenFileComparator = await FlutterLocalFileComparator.fromDefaultComparator(platform, log: print, fs: fs); + goldenFileComparator = await FlutterLocalFileComparator.fromLocalFileComparator( + localFileComparator: goldenFileComparator as LocalFileComparator, + platform, + log: print, + fs: fs, + ); } await testMain(); } @@ -239,22 +272,19 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { }); /// Creates a new [FlutterPostSubmitFileComparator] that mirrors the relative - /// path resolution of the default [goldenFileComparator]. + /// path resolution of the provided `localFileComparator`. /// - /// The [goldens] and [defaultComparator] parameters are visible for testing - /// purposes only. - static Future fromDefaultComparator( + /// The [goldens] parameter is visible for testing purposes only. + static Future fromLocalFileComparator( final Platform platform, { SkiaGoldClient? goldens, - LocalFileComparator? defaultComparator, + required LocalFileComparator localFileComparator, String? namePrefix, required LogCallback log, required FileSystem fs, }) async { - - defaultComparator ??= goldenFileComparator as LocalFileComparator; final Directory baseDirectory = FlutterGoldenFileComparator.getBaseDirectory( - defaultComparator, + localFileComparator, platform, suffix: 'flutter_goldens_postsubmit.', fs: fs, @@ -263,7 +293,13 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { goldens ??= SkiaGoldClient(baseDirectory, log: log); await goldens.auth(); - return FlutterPostSubmitFileComparator(baseDirectory.uri, goldens, namePrefix: namePrefix, log: log, fs: fs); + return FlutterPostSubmitFileComparator( + baseDirectory.uri, + goldens, + namePrefix: namePrefix, + log: log, + fs: fs, + ); } @override @@ -272,7 +308,6 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { golden = _addPrefix(golden); await update(golden, imageBytes); final File goldenFile = getGoldenFile(golden); - return skiaClient.imgtestAdd(golden.path, goldenFile); } @@ -285,7 +320,6 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { && !platform.environment.containsKey('GOLD_TRYJOB') // Only run on main branch. && _isMainBranch(platform.environment['GIT_BRANCH']); - return luciPostSubmit; } } @@ -322,21 +356,18 @@ class FlutterPreSubmitFileComparator extends FlutterGoldenFileComparator { /// Creates a new [FlutterPreSubmitFileComparator] that mirrors the /// relative path resolution of the default [goldenFileComparator]. /// - /// The [goldens] and [defaultComparator] parameters are visible for testing - /// purposes only. - static Future fromDefaultComparator( + /// The [goldens] parameter is visible for testing purposes only. + static Future fromLocalFileComparator( final Platform platform, { SkiaGoldClient? goldens, - LocalFileComparator? defaultComparator, + required LocalFileComparator localFileComparator, Directory? testBasedir, String? namePrefix, required LogCallback log, required FileSystem fs, }) async { - - defaultComparator ??= goldenFileComparator as LocalFileComparator; final Directory baseDirectory = testBasedir ?? FlutterGoldenFileComparator.getBaseDirectory( - defaultComparator, + localFileComparator, platform, suffix: 'flutter_goldens_presubmit.', fs: fs, @@ -416,18 +447,24 @@ class FlutterSkippingFileComparator extends FlutterGoldenFileComparator { final String reason; /// Creates a new [FlutterSkippingFileComparator] that mirrors the - /// relative path resolution of the default [goldenFileComparator]. - static FlutterSkippingFileComparator fromDefaultComparator( + /// relative path resolution of the given [localFileComparator]. + static FlutterSkippingFileComparator fromLocalFileComparator( String reason, { - LocalFileComparator? defaultComparator, + required LocalFileComparator localFileComparator, String? namePrefix, required LogCallback log, required FileSystem fs, }) { - defaultComparator ??= goldenFileComparator as LocalFileComparator; - final Uri basedir = defaultComparator.basedir; + final Uri basedir = localFileComparator.basedir; final SkiaGoldClient skiaClient = SkiaGoldClient(fs.directory(basedir), log: log); - return FlutterSkippingFileComparator(basedir, skiaClient, reason, namePrefix: namePrefix, log: log, fs: fs); + return FlutterSkippingFileComparator( + basedir, + skiaClient, + reason, + namePrefix: namePrefix, + log: log, + fs: fs, + ); } @override @@ -493,21 +530,20 @@ class FlutterLocalFileComparator extends FlutterGoldenFileComparator with LocalC }); /// Creates a new [FlutterLocalFileComparator] that mirrors the - /// relative path resolution of the default [goldenFileComparator]. + /// relative path resolution of the given [localFileComparator]. /// - /// The [goldens], [defaultComparator], and [baseDirectory] parameters are + /// The [goldens] and [baseDirectory] parameters are /// visible for testing purposes only. - static Future fromDefaultComparator( + static Future fromLocalFileComparator( final Platform platform, { SkiaGoldClient? goldens, - LocalFileComparator? defaultComparator, + required LocalFileComparator localFileComparator, Directory? baseDirectory, required LogCallback log, required FileSystem fs, }) async { - defaultComparator ??= goldenFileComparator as LocalFileComparator; baseDirectory ??= FlutterGoldenFileComparator.getBaseDirectory( - defaultComparator, + localFileComparator, platform, fs: fs, ); @@ -549,7 +585,12 @@ class FlutterLocalFileComparator extends FlutterGoldenFileComparator with LocalC ); } - return FlutterLocalFileComparator(baseDirectory.uri, goldens, log: log, fs: fs); + return FlutterLocalFileComparator( + baseDirectory.uri, + goldens, + log: log, + fs: fs, + ); } @override diff --git a/packages/flutter_goldens/pubspec.yaml b/packages/flutter_goldens/pubspec.yaml index 8d276bc01e58..52506643a4a6 100644 --- a/packages/flutter_goldens/pubspec.yaml +++ b/packages/flutter_goldens/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter crypto: 3.0.3 file: 7.0.0 - meta: 1.12.0 + meta: 1.14.0 platform: 3.1.4 process: 5.0.2 @@ -35,9 +35,9 @@ dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # 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.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: c1b6 +# PUBSPEC CHECKSUM: 6fba diff --git a/packages/flutter_goldens/test/flutter_goldens_test.dart b/packages/flutter_goldens/test/flutter_goldens_test.dart index 3ffcf703c2c8..c14236483547 100644 --- a/packages/flutter_goldens/test/flutter_goldens_test.dart +++ b/packages/flutter_goldens/test/flutter_goldens_test.dart @@ -13,6 +13,7 @@ import 'package:file/memory.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_goldens/flutter_goldens.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; import 'package:platform/platform.dart'; import 'package:process/process.dart'; @@ -851,7 +852,8 @@ void main() { fs.directory(_kFlutterRoot).createSync(recursive: true); final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient(); expect(fakeSkiaClient.initCalls, 0); - FlutterPostSubmitFileComparator.fromDefaultComparator( + FlutterPostSubmitFileComparator.fromLocalFileComparator( + localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix), platform, goldens: fakeSkiaClient, log: (String message) => fail('skia gold client printed unexpected output: "$message"'), @@ -859,119 +861,6 @@ void main() { ); expect(fakeSkiaClient.initCalls, 0); }); - - group('correctly determines testing environment', () { - test('returns true for configured Luci', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - 'GOLDCTL' : 'goldctl', - 'GIT_BRANCH' : 'master', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns false on release branches in postsubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GIT_BRANCH' : 'flutter-3.16-candidate.0', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns true on master branch in postsubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GIT_BRANCH' : 'master', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true on main branch in postsubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GIT_BRANCH' : 'main', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns false - GOLDCTL not present', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns false - GOLD_TRYJOB active', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - 'GOLDCTL' : 'goldctl', - 'GOLD_TRYJOB' : 'git/ref/12345/head', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns false - on Cirrus', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'CIRRUS_CI': 'true', - 'CIRRUS_PR': '', - 'CIRRUS_BRANCH': 'master', - 'GOLD_SERVICE_ACCOUNT': 'service account...', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - }); }); group('Pre-Submit', () { @@ -1049,7 +938,8 @@ void main() { fs.directory(_kFlutterRoot).createSync(recursive: true); final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient(); expect(fakeSkiaClient.tryInitCalls, 0); - FlutterPostSubmitFileComparator.fromDefaultComparator( + FlutterPostSubmitFileComparator.fromLocalFileComparator( + localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix), platform, goldens: fakeSkiaClient, log: (String message) => fail('skia gold client printed unexpected output: "$message"'), @@ -1057,214 +947,6 @@ void main() { ); expect(fakeSkiaClient.tryInitCalls, 0); }); - - group('correctly determines testing environment', () { - test('returns false on release branches in presubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GOLD_TRYJOB' : 'true', - 'GIT_BRANCH' : 'flutter-3.16-candidate.0', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns true on master branch in presubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GOLD_TRYJOB' : 'true', - 'GIT_BRANCH' : 'master', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true on main branch in presubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GOLD_TRYJOB' : 'true', - 'GIT_BRANCH' : 'main', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true for Luci', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - 'GOLDCTL' : 'goldctl', - 'GOLD_TRYJOB' : 'git/ref/12345/head', - 'GIT_BRANCH' : 'master', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns false - not on Luci', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns false - GOLDCTL missing', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - 'GOLD_TRYJOB' : 'git/ref/12345/head', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns false - GOLD_TRYJOB missing', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '12345678990', - 'GOLDCTL' : 'goldctl', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPreSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - - test('returns false - on Cirrus', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'CIRRUS_CI': 'true', - 'CIRRUS_PR': '', - 'CIRRUS_BRANCH': 'master', - 'GOLD_SERVICE_ACCOUNT': 'service account...', - }, - operatingSystem: 'macos', - ); - expect( - FlutterPostSubmitFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - }); - }); - - group('Skipping', () { - group('correctly determines testing environment', () { - test('returns true on release branches in presubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GOLD_TRYJOB' : 'true', - 'GIT_BRANCH' : 'flutter-3.16-candidate.0', - }, - operatingSystem: 'macos', - ); - expect( - FlutterSkippingFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true on release branches in postsubmit', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : 'sweet task ID', - 'GOLDCTL' : 'some/path', - 'GIT_BRANCH' : 'flutter-3.16-candidate.0', - }, - operatingSystem: 'macos', - ); - expect( - FlutterSkippingFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true on Cirrus builds', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'CIRRUS_CI' : 'yep', - }, - operatingSystem: 'macos', - ); - expect( - FlutterSkippingFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns true on irrelevant LUCI builds', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - 'SWARMING_TASK_ID' : '1234567890', - }, - operatingSystem: 'macos' - ); - expect( - FlutterSkippingFileComparator.isForEnvironment(platform), - isTrue, - ); - }); - - test('returns false - not in CI', () { - final FakePlatform platform = FakePlatform( - environment: { - 'FLUTTER_ROOT': _kFlutterRoot, - }, - operatingSystem: 'macos', - ); - expect( - FlutterSkippingFileComparator.isForEnvironment(platform), - isFalse, - ); - }); - }); }); group('Local', () { @@ -1358,7 +1040,8 @@ void main() { fakeDirectory.uri = Uri.parse('/flutter'); fakeSkiaClient.getExpectationForTestThrowable = const OSError("Can't reach Gold"); - final FlutterGoldenFileComparator comparator1 = await FlutterLocalFileComparator.fromDefaultComparator( + final FlutterGoldenFileComparator comparator1 = await FlutterLocalFileComparator.fromLocalFileComparator( + localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix), platform, goldens: fakeSkiaClient, baseDirectory: fakeDirectory, @@ -1368,7 +1051,8 @@ void main() { expect(comparator1.runtimeType, FlutterSkippingFileComparator); fakeSkiaClient.getExpectationForTestThrowable = const SocketException("Can't reach Gold"); - final FlutterGoldenFileComparator comparator2 = await FlutterLocalFileComparator.fromDefaultComparator( + final FlutterGoldenFileComparator comparator2 = await FlutterLocalFileComparator.fromLocalFileComparator( + localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix), platform, goldens: fakeSkiaClient, baseDirectory: fakeDirectory, @@ -1378,7 +1062,8 @@ void main() { expect(comparator2.runtimeType, FlutterSkippingFileComparator); fakeSkiaClient.getExpectationForTestThrowable = const FormatException("Can't reach Gold"); - final FlutterGoldenFileComparator comparator3 = await FlutterLocalFileComparator.fromDefaultComparator( + final FlutterGoldenFileComparator comparator3 = await FlutterLocalFileComparator.fromLocalFileComparator( + localFileComparator: LocalFileComparator(Uri.parse('/test'), pathStyle: path.Style.posix), platform, goldens: fakeSkiaClient, baseDirectory: fakeDirectory, diff --git a/packages/flutter_localizations/README.md b/packages/flutter_localizations/README.md index 96ae11036eb2..26a63506d0da 100644 --- a/packages/flutter_localizations/README.md +++ b/packages/flutter_localizations/README.md @@ -20,7 +20,7 @@ and `WidgetsLocalizations`, with appropriate name substitutions): String get showMenuTooltip; ``` to the localizations class `MaterialLocalizations`, - in [`packages/flutter/lib/src/material/material_localizations.dart`](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/material_localizations.dart); + in [`packages/flutter/lib/src/material/material_localizations.dart`](https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/material/material_localizations.dart); ### For messages with parameters, add new function ``` @@ -41,7 +41,7 @@ and `WidgetsLocalizations`, with appropriate name substitutions): @override String aboutListTileTitle(String applicationName) => 'About $applicationName'; ``` - For messages with parameters, do also add the function to `GlobalMaterialLocalizations` in [`packages/flutter_localizations/lib/src/material_localizations.dart`](https://github.com/flutter/flutter/blob/master/packages/flutter_localizations/lib/src/material_localizations.dart), and add a raw getter as demonstrated below: + For messages with parameters, do also add the function to `GlobalMaterialLocalizations` in [`packages/flutter_localizations/lib/src/material_localizations.dart`](https://github.com/flutter/flutter/blob/main/packages/flutter_localizations/lib/src/material_localizations.dart), and add a raw getter as demonstrated below: ``` /// The raw version of [aboutListTileTitle], with `$applicationName` verbatim diff --git a/packages/flutter_localizations/pubspec.yaml b/packages/flutter_localizations/pubspec.yaml index 647c20fa5425..af018294da2e 100644 --- a/packages/flutter_localizations/pubspec.yaml +++ b/packages/flutter_localizations/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.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" @@ -37,7 +37,7 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: ec97 +# PUBSPEC CHECKSUM: bf9b diff --git a/packages/flutter_test/lib/fix_data/README.md b/packages/flutter_test/lib/fix_data/README.md index 14950061d9af..2b7b48e81e2b 100644 --- a/packages/flutter_test/lib/fix_data/README.md +++ b/packages/flutter_test/lib/fix_data/README.md @@ -38,7 +38,7 @@ format do not break Flutter. See [tools/bots/flutter/analyze_flutter_flutter.sh](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_flutter.sh) for where the flutter fix tests are invoked for the dart repo. -See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) +See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/main/dev/bots/test.dart) for where the flutter fix tests are invoked for the flutter/flutter repo. When possible, please coordinate changes to this directory that might affect the diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 95a80d2f3eb0..87fbce0398af 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -2098,7 +2098,11 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { /// /// The resulting ViewConfiguration maps the given size onto the actual display /// using the [BoxFit.contain] algorithm. -class TestViewConfiguration extends ViewConfiguration { +/// +/// If the underlying [FlutterView] changes, a new [TestViewConfiguration] should +/// be created. See [RendererBinding.handleMetricsChanged] and +/// [RendererBinding.createViewConfigurationFor]. +class TestViewConfiguration implements ViewConfiguration { /// Deprecated. Will be removed in a future version of Flutter. /// /// This property has been deprecated to prepare for Flutter's upcoming @@ -2120,14 +2124,29 @@ class TestViewConfiguration extends ViewConfiguration { /// Creates a [TestViewConfiguration] with the given size and view. /// /// The [size] defaults to 800x600. - TestViewConfiguration.fromView({required ui.FlutterView view, Size size = _kDefaultTestViewportSize}) - : _paintMatrix = _getMatrix(size, view.devicePixelRatio, view), - _physicalSize = view.physicalSize, - super( - devicePixelRatio: view.devicePixelRatio, - logicalConstraints: BoxConstraints.tight(size), - physicalConstraints: BoxConstraints.tight(size) * view.devicePixelRatio, - ); + /// + /// The settings of the given [FlutterView] are captured when the constructor + /// is called, and subsequent changes are ignored. A new + /// [TestViewConfiguration] should be created if the underlying [FlutterView] + /// changes. See [RendererBinding.handleMetricsChanged] and + /// [RendererBinding.createViewConfigurationFor]. + TestViewConfiguration.fromView({ + required ui.FlutterView view, + Size size = _kDefaultTestViewportSize, + }) : devicePixelRatio = view.devicePixelRatio, + logicalConstraints = BoxConstraints.tight(size), + physicalConstraints = BoxConstraints.tight(size) * view.devicePixelRatio, + _paintMatrix = _getMatrix(size, view.devicePixelRatio, view), + _physicalSize = view.physicalSize; + + @override + final double devicePixelRatio; + + @override + final BoxConstraints logicalConstraints; + + @override + final BoxConstraints physicalConstraints; static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.FlutterView window) { final double inverseRatio = devicePixelRatio / window.devicePixelRatio; @@ -2158,6 +2177,18 @@ class TestViewConfiguration extends ViewConfiguration { @override Matrix4 toMatrix() => _paintMatrix.clone(); + @override + bool shouldUpdateMatrix(ViewConfiguration oldConfiguration) { + if (oldConfiguration.runtimeType != runtimeType) { + // New configuration could have different logic, so we don't know + // whether it will need a new transform. Return a conservative result. + return true; + } + oldConfiguration as TestViewConfiguration; + // Compare the matrices directly since they are cached. + return oldConfiguration._paintMatrix != _paintMatrix; + } + final Size _physicalSize; @override diff --git a/packages/flutter_test/lib/src/goldens.dart b/packages/flutter_test/lib/src/goldens.dart index cb73450c21bc..3e1e2b9d6802 100644 --- a/packages/flutter_test/lib/src/goldens.dart +++ b/packages/flutter_test/lib/src/goldens.dart @@ -127,11 +127,7 @@ abstract class GoldenFileComparator { /// /// * [flutter_test] for more information about how to configure tests at the /// directory-level. -GoldenFileComparator get goldenFileComparator => _goldenFileComparator; -GoldenFileComparator _goldenFileComparator = const TrivialComparator._(); -set goldenFileComparator(GoldenFileComparator value) { - _goldenFileComparator = value; -} +GoldenFileComparator goldenFileComparator = const TrivialComparator._(); /// Compares image pixels against a golden image file. /// diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index c0c09b48ab07..3b5cefa6009d 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading # this, make sure the tests are still running correctly. - test_api: 0.7.0 + test_api: 0.7.1 matcher: 0.12.16+1 # Used by golden file comparator @@ -43,14 +43,14 @@ dependencies: leak_tracker: 10.0.5 # 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" material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: file: 7.0.0 -# PUBSPEC CHECKSUM: 804d +# PUBSPEC CHECKSUM: 5351 diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 07f238445893..dbe95b25f486 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -53,6 +53,9 @@ void main() { group('the group with retry flag', () { testWidgets('the test inside it', (WidgetTester tester) async { addTearDown(() => retried = true); + if (!retried) { + debugPrint('DISREGARD NEXT FAILURE, IT IS EXPECTED'); + } expect(retried, isTrue); }); }, retry: 1); @@ -62,6 +65,9 @@ void main() { bool retried = false; testWidgets('the test with retry flag', (WidgetTester tester) async { addTearDown(() => retried = true); + if (!retried) { + debugPrint('DISREGARD NEXT FAILURE, IT IS EXPECTED'); + } expect(retried, isTrue); }, retry: 1); }); @@ -557,6 +563,7 @@ void main() { }; final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); + debugPrint('DISREGARD NEXT PENDING TIMER LIST, IT IS EXPECTED'); await binding.runTest(() async { final Timer timer = Timer(const Duration(seconds: 1), () {}); expect(timer.isActive, true); diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh index 71f69ef22e15..784671936810 100755 --- a/packages/flutter_tools/bin/macos_assemble.sh +++ b/packages/flutter_tools/bin/macos_assemble.sh @@ -144,11 +144,23 @@ BuildApp() { if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then flutter_args+=("-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}") fi - flutter_args+=("${build_mode}_macos_bundle_flutter_assets") + + # Run flutter assemble with the build mode specific target that was passed in. + # If no target was passed it, default to build mode specific + # macos_bundle_flutter_assets target. + if [[ -n "$1" ]]; then + flutter_args+=("${build_mode}$1") + else + flutter_args+=("${build_mode}_macos_bundle_flutter_assets") + fi RunCommand "${flutter_args[@]}" } +PrepareFramework() { + BuildApp "_unpack_macos" +} + # Adds the App.framework as an embedded binary, the flutter_assets as # resources, and the native assets. EmbedFrameworks() { @@ -192,5 +204,7 @@ else BuildApp ;; "embed") EmbedFrameworks ;; + "prepare") + PrepareFramework ;; esac fi diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb index 3d64737ae691..64e163ea85d3 100644 --- a/packages/flutter_tools/bin/podhelper.rb +++ b/packages/flutter_tools/bin/podhelper.rb @@ -302,7 +302,10 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl system('mkdir', '-p', symlink_plugins_dir) plugins_file = File.join(application_path, '..', '.flutter-plugins-dependencies') - plugin_pods = flutter_parse_plugins_file(plugins_file, platform) + dependencies_hash = flutter_parse_plugins_file(plugins_file) + plugin_pods = flutter_get_plugins_list(dependencies_hash, platform) + swift_package_manager_enabled = flutter_get_swift_package_manager_enabled(dependencies_hash) + plugin_pods.each do |plugin_hash| plugin_name = plugin_hash['name'] plugin_path = plugin_hash['path'] @@ -319,25 +322,43 @@ def flutter_install_plugin_pods(application_path = nil, relative_symlink_dir, pl # Keep pod path relative so it can be checked into Podfile.lock. relative = flutter_relative_path_from_podfile(symlink) + # If Swift Package Manager is enabled and the plugin has a Package.swift, + # skip from installing as a pod. + swift_package_exists = File.exist?(File.join(relative, platform_directory, plugin_name, "Package.swift")) + next if swift_package_manager_enabled && swift_package_exists + + # If a plugin is Swift Package Manager compatible but not CocoaPods compatible, skip it. + # The tool will print an error about it. + next if swift_package_exists && !File.exist?(File.join(relative, platform_directory, plugin_name + ".podspec")) + pod plugin_name, path: File.join(relative, platform_directory) end end -# .flutter-plugins-dependencies format documented at -# https://flutter.dev/go/plugins-list-migration -def flutter_parse_plugins_file(file, platform) +def flutter_parse_plugins_file(file) file_path = File.expand_path(file) return [] unless File.exist? file_path dependencies_file = File.read(file) - dependencies_hash = JSON.parse(dependencies_file) + JSON.parse(dependencies_file) +end +# .flutter-plugins-dependencies format documented at +# https://flutter.dev/go/plugins-list-migration +def flutter_get_plugins_list(dependencies_hash, platform) # dependencies_hash.dig('plugins', 'ios') not available until Ruby 2.3 + return [] unless dependencies_hash.any? return [] unless dependencies_hash.has_key?('plugins') return [] unless dependencies_hash['plugins'].has_key?(platform) dependencies_hash['plugins'][platform] || [] end +def flutter_get_swift_package_manager_enabled(dependencies_hash) + return false unless dependencies_hash.any? + return false unless dependencies_hash.has_key?('swift_package_manager_enabled') + dependencies_hash['swift_package_manager_enabled'] == true +end + def flutter_relative_path_from_podfile(path) # defined_in_file is set by CocoaPods and is a Pathname to the Podfile. project_directory_pathname = defined_in_file.dirname diff --git a/packages/flutter_tools/bin/xcode_backend.dart b/packages/flutter_tools/bin/xcode_backend.dart index dca13dad5f74..f657defd98ce 100644 --- a/packages/flutter_tools/bin/xcode_backend.dart +++ b/packages/flutter_tools/bin/xcode_backend.dart @@ -49,6 +49,8 @@ class Context { switch (subCommand) { case 'build': buildApp(); + case 'prepare': + prepare(); case 'thin': // No-op, thinning is handled during the bundle asset assemble build target. break; @@ -351,21 +353,67 @@ class Context { } } + void prepare() { + final bool verbose = (environment['VERBOSE_SCRIPT_LOGGING'] ?? '').isNotEmpty; + final String sourceRoot = environment['SOURCE_ROOT'] ?? ''; + final String projectPath = environment['FLUTTER_APPLICATION_PATH'] ?? '$sourceRoot/..'; + + final String buildMode = parseFlutterBuildMode(); + + final List flutterArgs = _generateFlutterArgsForAssemble(buildMode, verbose); + + flutterArgs.add('${buildMode}_unpack_ios'); + + final ProcessResult result = runSync( + '${environmentEnsure('FLUTTER_ROOT')}/bin/flutter', + flutterArgs, + verbose: verbose, + allowFail: true, + workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}" + ); + + if (result.exitCode != 0) { + echoError('Failed to copy Flutter framework.'); + exitApp(-1); + } + } + void buildApp() { - final bool verbose = environment['VERBOSE_SCRIPT_LOGGING'] != null && environment['VERBOSE_SCRIPT_LOGGING'] != ''; + final bool verbose = (environment['VERBOSE_SCRIPT_LOGGING'] ?? '').isNotEmpty; final String sourceRoot = environment['SOURCE_ROOT'] ?? ''; - String projectPath = '$sourceRoot/..'; - if (environment['FLUTTER_APPLICATION_PATH'] != null) { - projectPath = environment['FLUTTER_APPLICATION_PATH']!; + final String projectPath = environment['FLUTTER_APPLICATION_PATH'] ?? '$sourceRoot/..'; + + final String buildMode = parseFlutterBuildMode(); + + final List flutterArgs = _generateFlutterArgsForAssemble(buildMode, verbose); + + flutterArgs.add('${buildMode}_ios_bundle_flutter_assets'); + + final ProcessResult result = runSync( + '${environmentEnsure('FLUTTER_ROOT')}/bin/flutter', + flutterArgs, + verbose: verbose, + allowFail: true, + workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}" + ); + + if (result.exitCode != 0) { + echoError('Failed to package $projectPath.'); + exitApp(-1); } + streamOutput('done'); + streamOutput(' └─Compiling, linking and signing...'); + + echo('Project $projectPath built and packaged successfully.'); + } + + List _generateFlutterArgsForAssemble(String buildMode, bool verbose) { String targetPath = 'lib/main.dart'; if (environment['FLUTTER_TARGET'] != null) { targetPath = environment['FLUTTER_TARGET']!; } - final String buildMode = parseFlutterBuildMode(); - // Warn the user if not archiving (ACTION=install) in release mode. final String? action = environment['ACTION']; if (action == 'install' && buildMode != 'release') { @@ -432,24 +480,6 @@ class Context { flutterArgs.add('-dCodeSizeDirectory=${environment['CODE_SIZE_DIRECTORY']}'); } - flutterArgs.add('${buildMode}_ios_bundle_flutter_assets'); - - final ProcessResult result = runSync( - '${environmentEnsure('FLUTTER_ROOT')}/bin/flutter', - flutterArgs, - verbose: verbose, - allowFail: true, - workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}" - ); - - if (result.exitCode != 0) { - echoError('Failed to package $projectPath.'); - exitApp(-1); - } - - streamOutput('done'); - streamOutput(' └─Compiling, linking and signing...'); - - echo('Project $projectPath built and packaged successfully.'); + return flutterArgs; } } diff --git a/packages/flutter_tools/doc/attach.md b/packages/flutter_tools/doc/attach.md index 565115ded3f5..247f9ae057ec 100644 --- a/packages/flutter_tools/doc/attach.md +++ b/packages/flutter_tools/doc/attach.md @@ -23,4 +23,4 @@ known, it can be explicitly provided to attach via the command-line, e.g. ## Source -See the [source](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/commands/attach.dart) for the attach command. +See the [source](https://github.com/flutter/flutter/blob/main/packages/flutter_tools/lib/src/commands/attach.dart) for the attach command. diff --git a/packages/flutter_tools/doc/daemon.md b/packages/flutter_tools/doc/daemon.md index f570949064de..9d6cabd954ab 100644 --- a/packages/flutter_tools/doc/daemon.md +++ b/packages/flutter_tools/doc/daemon.md @@ -300,7 +300,7 @@ The following subset of the app domain is available in `flutter run --machine`. ## Source -See the [source](https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/commands/daemon.dart) for the daemon protocol and implementation. +See the [source](https://github.com/flutter/flutter/blob/main/packages/flutter_tools/lib/src/commands/daemon.dart) for the daemon protocol and implementation. ## Changelog diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy index 1e9d4c639259..5cda294e63dc 100644 --- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy @@ -1148,9 +1148,9 @@ class FlutterPlugin implements Plugin { bundleSkSLPathValue = project.property(propBundleSkslPath) } String performanceMeasurementFileValue - final String propPerformanceMesaurementFile = "performance-measurement-file" - if (project.hasProperty(propPerformanceMesaurementFile)) { - performanceMeasurementFileValue = project.property(propPerformanceMesaurementFile) + final String propPerformanceMeasurementFile = "performance-measurement-file" + if (project.hasProperty(propPerformanceMeasurementFile)) { + performanceMeasurementFileValue = project.property(propPerformanceMeasurementFile) } String codeSizeDirectoryValue final String propCodeSizeDirectory = "code-size-directory" diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index ae80be062826..e8014321b6e1 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -92,7 +92,7 @@ Future run( // TODO(eliasyishak): Set the telemetry for the unified_analytics // package as well, the above will be removed once we have - // fully transitioned to using the new package + // fully transitioned to using the new package, https://github.com/flutter/flutter/issues/128251 await globals.analytics.setTelemetry(false); } @@ -111,10 +111,21 @@ Future run( // TODO(eliasyishak): Set the telemetry for the unified_analytics // package as well, the above will be removed once we have - // fully transitioned to using the new package + // fully transitioned to using the new package, https://github.com/flutter/flutter/issues/128251 await globals.analytics.setTelemetry(true); } + // Send an event to GA3 for any users that are opted into GA3 + // analytics but have opted out of GA4 (package:unified_analytics) + // TODO(eliasyishak): remove once GA3 sunset, https://github.com/flutter/flutter/issues/128251 + if (!globals.analytics.telemetryEnabled && + globals.flutterUsage.enabled) { + UsageEvent( + 'ga4_and_ga3_status_mismatch', + 'opted_out_of_ga4', + flutterUsage: globals.flutterUsage, + ).send(); + } await runner.run(args); diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index ba3703b86ef6..efb6e9b4d263 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -578,7 +578,7 @@ class CachedArtifacts implements Artifacts { case TargetPlatform.linux_arm64: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: - return _getDesktopArtifactPath(artifact, platform, mode); + return _getDesktopArtifactPath(artifact, platform!, mode); case TargetPlatform.fuchsia_arm64: case TargetPlatform.fuchsia_x64: return _getFuchsiaArtifactPath(artifact, platform!, mode!); @@ -594,18 +594,18 @@ class CachedArtifacts implements Artifacts { return _fileSystem.path.basename(_getEngineArtifactsPath(platform, mode)!); } - String _getDesktopArtifactPath(Artifact artifact, TargetPlatform? platform, BuildMode? mode) { + String _getDesktopArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode? mode) { // When platform is null, a generic host platform artifact is being requested // and not the gen_snapshot for darwin as a target platform. - if (platform != null && artifact == Artifact.genSnapshot) { + if (artifact == Artifact.genSnapshot) { final String engineDir = _getEngineArtifactsPath(platform, mode)!; return _fileSystem.path.join(engineDir, _artifactToFileName(artifact, _platform)); } - if (platform != null && artifact == Artifact.flutterMacOSFramework) { + if (artifact == Artifact.flutterMacOSFramework) { final String engineDir = _getEngineArtifactsPath(platform, mode)!; return _getMacOSEngineArtifactPath(engineDir, _fileSystem, _platform); } - return _getHostArtifactPath(artifact, platform ?? _currentHostPlatform(_platform, _operatingSystemUtils), mode); + return _getHostArtifactPath(artifact, platform, mode); } String _getAndroidArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) { diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 60be3c0e1d7d..09e57962c628 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -16,7 +16,7 @@ import 'process.dart'; class SnapshotType { SnapshotType(this.platform, this.mode); - final TargetPlatform? platform; + final TargetPlatform platform; final BuildMode mode; @override diff --git a/packages/flutter_tools/lib/src/base/error_handling_io.dart b/packages/flutter_tools/lib/src/base/error_handling_io.dart index 468bfbf70573..a1e5283bfc24 100644 --- a/packages/flutter_tools/lib/src/base/error_handling_io.dart +++ b/packages/flutter_tools/lib/src/base/error_handling_io.dart @@ -728,7 +728,7 @@ void _handlePosixException(Exception e, String? message, int errorCode, String? // From: // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h - // https://github.com/apple/darwin-xnu/blob/master/bsd/dev/dtrace/scripts/errno.d + // https://github.com/apple/darwin-xnu/blob/main/bsd/dev/dtrace/scripts/errno.d const int eperm = 1; const int enospc = 28; const int eacces = 13; @@ -762,7 +762,7 @@ void _handlePosixException(Exception e, String? message, int errorCode, String? } void _handleMacOSException(Exception e, String? message, int errorCode, String? posixPermissionSuggestion) { - // https://github.com/apple/darwin-xnu/blob/master/bsd/dev/dtrace/scripts/errno.d + // https://github.com/apple/darwin-xnu/blob/main/bsd/dev/dtrace/scripts/errno.d const int ebadarch = 86; if (errorCode == ebadarch) { final StringBuffer errorBuffer = StringBuffer(); diff --git a/packages/flutter_tools/lib/src/base/terminal.dart b/packages/flutter_tools/lib/src/base/terminal.dart index 4edce4995764..0c95fb390042 100644 --- a/packages/flutter_tools/lib/src/base/terminal.dart +++ b/packages/flutter_tools/lib/src/base/terminal.dart @@ -229,7 +229,7 @@ class AnsiTerminal implements Terminal { // Assume unicode emojis are supported when not on Windows. // If we are on Windows, unicode emojis are supported in Windows Terminal, // which sets the WT_SESSION environment variable. See: - // https://github.com/microsoft/terminal/blob/master/doc/user-docs/index.md#tips-and-tricks + // https://learn.microsoft.com/en-us/windows/terminal/tips-and-tricks @override bool get supportsEmoji => !_platform.isWindows || _platform.environment.containsKey('WT_SESSION'); diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index 26a8278ce75a..165120a78388 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -193,19 +193,19 @@ class UserMessages { String cocoaPodsMissing(String consequence, String installInstructions) => 'CocoaPods not installed.\n' '$consequence\n' - 'To install $installInstructions'; + 'For installation instructions, $installInstructions'; String cocoaPodsUnknownVersion(String consequence, String upgradeInstructions) => 'Unknown CocoaPods version installed.\n' '$consequence\n' - 'To upgrade $upgradeInstructions'; + 'To update CocoaPods, $upgradeInstructions'; String cocoaPodsOutdated(String currentVersion, String recVersion, String consequence, String upgradeInstructions) => 'CocoaPods $currentVersion out of date ($recVersion is recommended).\n' '$consequence\n' - 'To upgrade $upgradeInstructions'; + 'To update CocoaPods, $upgradeInstructions'; String cocoaPodsBrokenInstall(String consequence, String reinstallInstructions) => 'CocoaPods installed but not working.\n' '$consequence\n' - 'To re-install $reinstallInstructions'; + 'For re-installation instructions, $reinstallInstructions'; // Messages used in VisualStudioValidator String visualStudioVersion(String name, String version) => '$name version $version'; diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index 795cbf77da79..4ffc08b9397d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -15,6 +15,7 @@ import '../../devfs.dart'; import '../../flutter_manifest.dart'; import '../build_system.dart'; import '../depfile.dart'; +import '../exceptions.dart'; import '../tools/asset_transformer.dart'; import '../tools/scene_importer.dart'; import '../tools/shader_compiler.dart'; @@ -35,7 +36,7 @@ Future copyAssets( Directory outputDirectory, { Map additionalContent = const {}, required TargetPlatform targetPlatform, - BuildMode? buildMode, + required BuildMode buildMode, List additionalInputs = const [], String? flavor, }) async { @@ -101,6 +102,7 @@ Future copyAssets( processManager: environment.processManager, fileSystem: environment.fileSystem, dartBinaryPath: environment.artifacts.getArtifactPath(Artifact.engineDartBinary), + buildMode: buildMode, ); final Map assetEntries = { @@ -148,6 +150,7 @@ Future copyAssets( outputPath: file.path, workingDirectory: environment.projectDir.path, transformerEntries: entry.value.transformers, + logger: environment.logger, ); doCopy = false; if (failure != null) { @@ -186,7 +189,7 @@ Future copyAssets( // Copy deferred components assets only for release or profile builds. // The assets are included in assetBundle.entries as a normal asset when // building as debug. - if (environment.defines[kDeferredComponents] == 'true' && buildMode != null) { + if (environment.defines[kDeferredComponents] == 'true') { await Future.wait(assetBundle.deferredComponentsEntries.entries.map>( (MapEntry> componentEntries) async { final Directory componentOutputDir = @@ -343,6 +346,11 @@ class CopyAssets extends Target { @override Future build(Environment environment) async { + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); final Directory output = environment .buildDir .childDirectory('flutter_assets'); @@ -351,6 +359,7 @@ class CopyAssets extends Target { environment, output, targetPlatform: TargetPlatform.android, + buildMode: buildMode, flavor: environment.defines[kFlavor], ); environment.depFileService.writeToFile( diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index ba03270c526d..258eaa86185e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:package_config/package_config.dart'; +import 'package:yaml/yaml.dart'; import '../../artifacts.dart'; import '../../base/build.dart'; @@ -121,15 +122,17 @@ class ReleaseCopyFlutterBundle extends CopyFlutterBundle { /// even though it is not listed as an input. Pub inserts a timestamp into /// the file which causes unnecessary rebuilds, so instead a subset of the contents /// are used an input instead. -class KernelSnapshot extends Target { - const KernelSnapshot(); +/// +/// This kernel snapshot is concatenated with the [KernelSnapshotNativeAssets] +/// inside [KernelSnapshot] byte-wise to create the combined kernel snapshot. +class KernelSnapshotProgram extends Target { + const KernelSnapshotProgram(); @override - String get name => 'kernel_snapshot'; + String get name => 'kernel_snapshot_program'; @override List get inputs => const [ - Source.pattern('{BUILD_DIR}/native_assets.yaml'), Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'), Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'), Source.artifact(Artifact.platformKernelDill), @@ -139,7 +142,9 @@ class KernelSnapshot extends Target { ]; @override - List get outputs => const []; + List get outputs => const [ + // TODO(mosuem): Should output resources.json. https://github.com/flutter/flutter/issues/146263 + ]; @override List get depfiles => [ @@ -148,11 +153,12 @@ class KernelSnapshot extends Target { @override List get dependencies => const [ - NativeAssets(), GenerateLocalizationsTarget(), DartPluginRegistrantTarget(), ]; + static const String dillName = 'program.dill'; + @override Future build(Environment environment) async { final KernelCompiler compiler = KernelCompiler( @@ -186,13 +192,6 @@ class KernelSnapshot extends Target { final List? fileSystemRoots = environment.defines[kFileSystemRoots]?.split(','); final String? fileSystemScheme = environment.defines[kFileSystemScheme]; - final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml'); - final String nativeAssets = nativeAssetsFile.path; - if (!await nativeAssetsFile.exists()) { - throwToolExit("$nativeAssets doesn't exist."); - } - environment.logger.printTrace('Embedding native assets mapping $nativeAssets in kernel.'); - TargetModel targetModel = TargetModel.flutter; if (targetPlatform == TargetPlatform.fuchsia_x64 || targetPlatform == TargetPlatform.fuchsia_arm64) { @@ -242,6 +241,8 @@ class KernelSnapshot extends Target { logger: environment.logger, ); + final String dillPath = environment.buildDir.childFile(dillName).path; + final CompilerOutput? output = await compiler.compile( sdkRoot: environment.artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -252,9 +253,8 @@ class KernelSnapshot extends Target { buildMode: buildMode, trackWidgetCreation: trackWidgetCreation && buildMode != BuildMode.release, targetModel: targetModel, - outputFilePath: environment.buildDir.childFile('app.dill').path, - initializeFromDill: buildMode.isPrecompiled ? null : - environment.buildDir.childFile('app.dill').path, + outputFilePath: dillPath, + initializeFromDill: buildMode.isPrecompiled ? null : dillPath, packagesPath: packagesFile.path, linkPlatformKernelIn: forceLinkPlatform || buildMode.isPrecompiled, mainPath: targetFileAbsolute, @@ -268,6 +268,109 @@ class KernelSnapshot extends Target { buildDir: environment.buildDir, targetOS: targetOS, checkDartPluginRegistry: environment.generateDartPluginRegistry, + ); + if (output == null || output.errorCount != 0) { + throw Exception(); + } + } +} + +/// Generate a kernel snapshot of the native assets mapping for resolving +/// `@Native` assets at runtime. +/// +/// This kernel snapshot is concatenated to the [KernelSnapshotProgram] +/// inside [KernelSnapshot] to create the combined kernel snapshot. +class KernelSnapshotNativeAssets extends Target { + const KernelSnapshotNativeAssets(); + + @override + String get name => 'kernel_snapshot_native_assets'; + + @override + List get inputs => [ + const Source.pattern('{BUILD_DIR}/native_assets.yaml'), + ...const KernelSnapshotProgram().inputs, + ]; + + @override + List get outputs => const []; + + @override + List get depfiles => const []; + + @override + List get dependencies => [ + const NativeAssets(), + ]; + + static const String dillName = 'native_assets.dill'; + + @override + Future build(Environment environment) async { + final File nativeAssetsFile = environment.buildDir.childFile('native_assets.yaml'); + final File dillFile = environment.buildDir.childFile(dillName); + + final YamlNode nativeAssetContents = loadYamlNode(await nativeAssetsFile.readAsString()); + final Object? nativeAssetsInYaml = (nativeAssetContents as Map)['native-assets']; + if (nativeAssetsInYaml is! Map || nativeAssetsInYaml.isEmpty) { + // Write an empty file to make concatenation a no-op. + // Write the file out to disk for caching. + await dillFile.writeAsBytes([]); + return; + } + + final KernelCompiler compiler = KernelCompiler( + fileSystem: environment.fileSystem, + logger: environment.logger, + processManager: environment.processManager, + artifacts: environment.artifacts, + fileSystemRoots: [], + ); + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, 'kernel_snapshot'); + } + final String? targetPlatformEnvironment = environment.defines[kTargetPlatform]; + if (targetPlatformEnvironment == null) { + throw MissingDefineException(kTargetPlatform, 'kernel_snapshot'); + } + final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); + final File packagesFile = environment.projectDir + .childDirectory('.dart_tool') + .childFile('package_config.json'); + + final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment); + + final String? frontendServerStarterPath = environment.defines[kFrontendServerStarterPath]; + + final String nativeAssets = nativeAssetsFile.path; + if (!await nativeAssetsFile.exists()) { + throwToolExit("$nativeAssets doesn't exist."); + } + environment.logger.printTrace('Embedding native assets mapping $nativeAssets in kernel.'); + + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + packagesFile, + logger: environment.logger, + ); + + final String dillPath = dillFile.path; + + final CompilerOutput? output = await compiler.compile( + sdkRoot: environment.artifacts.getArtifactPath( + Artifact.flutterPatchedSdkPath, + platform: targetPlatform, + mode: buildMode, + ), + aot: buildMode.isPrecompiled, + buildMode: buildMode, + trackWidgetCreation: false, + outputFilePath: dillPath, + packagesPath: packagesFile.path, + frontendServerStarterPath: frontendServerStarterPath, + packageConfig: packageConfig, + buildDir: environment.buildDir, + dartDefines: [], nativeAssets: nativeAssets, ); if (output == null || output.errorCount != 0) { @@ -276,6 +379,43 @@ class KernelSnapshot extends Target { } } +class KernelSnapshot extends Target { + const KernelSnapshot(); + + @override + String get name => 'kernel_snapshot'; + + @override + List get dependencies => const [ + KernelSnapshotProgram(), + KernelSnapshotNativeAssets(), + ]; + + @override + List get inputs => []; + + @override + List get outputs => []; + + static const String dillName = 'app.dill'; + + @override + Future build(Environment environment) async { + final File programDill = environment.buildDir.childFile( + KernelSnapshotProgram.dillName, + ); + final File nativeAssetsDill = environment.buildDir.childFile( + KernelSnapshotNativeAssets.dillName, + ); + final File dill = environment.buildDir.childFile(dillName); + await programDill.copy(dill.path); + await dill.writeAsBytes( + await nativeAssetsDill.readAsBytes(), + mode: FileMode.append, + ); + } +} + /// Supports compiling a dart kernel file to an ELF binary. abstract class AotElfBase extends Target { const AotElfBase(); diff --git a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart index 916d1b3f5929..d8848d912254 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart @@ -327,15 +327,25 @@ class IconTreeShaker { final Object? package = iconDataMap['fontPackage']; final Object? fontFamily = iconDataMap['fontFamily']; final Object? codePoint = iconDataMap['codePoint']; - if ((package ?? '') is! String || // Null is ok here. - fontFamily is! String || + if ((package ?? '') is! String || + (fontFamily ?? '') is! String || codePoint is! num) { throw IconTreeShakerException._( 'Invalid ConstFinder result. Expected "fontPackage" to be a String, ' '"fontFamily" to be a String, and "codePoint" to be an int, ' 'got: $iconDataMap.'); } - final String family = fontFamily; + if (fontFamily == null) { + _logger.printTrace( + 'Expected to find fontFamily for constant IconData with codepoint: ' + '$codePoint, but found fontFamily: $fontFamily. This usually means ' + 'you are relying on the system font. Alternatively, font families in ' + 'an IconData class can be provided in the assets section of your ' + 'pubspec.yaml, or you are missing "uses-material-design: true".', + ); + continue; + } + final String family = fontFamily as String; final String key = package == null ? family : 'packages/$package/$family'; diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 9ca6e543a206..1fd586e063f3 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -503,6 +503,7 @@ abstract class IosAssetBundle extends Target { environment, assetDirectory, targetPlatform: TargetPlatform.ios, + buildMode: buildMode, additionalInputs: [ flutterProject.ios.infoPlist, flutterProject.ios.appFrameworkInfoPlist, diff --git a/packages/flutter_tools/lib/src/build_system/targets/linux.dart b/packages/flutter_tools/lib/src/build_system/targets/linux.dart index bc446c5fa652..6f761f27b95f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/linux.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/linux.dart @@ -137,6 +137,7 @@ abstract class BundleLinuxAssets extends Target { environment, outputDirectory, targetPlatform: targetPlatform, + buildMode: buildMode, additionalContent: { 'version.json': DevFSStringContent(versionInfo), }, diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart index 9996664a0a1e..2e018fe84ccc 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -438,6 +438,7 @@ abstract class MacOSBundleFlutterAssets extends Target { environment, assetDirectory, targetPlatform: TargetPlatform.darwin, + buildMode: buildMode, flavor: environment.defines[kFlavor], ); environment.depFileService.writeToFile( diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index e2ab35de1090..4775a262d698 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -371,13 +371,17 @@ class NativeAssets extends Target { ]; @override - List get dependencies => []; + List get dependencies => const [ + // In AOT, depends on tree-shaking information (resources.json) from compiling dart. + KernelSnapshotProgram(), + ]; @override List get inputs => const [ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart'), // If different packages are resolved, different native assets might need to be built. Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'), + // TODO(mosuem): Should consume resources.json. https://github.com/flutter/flutter/issues/146263 ]; @override diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index d7c7f27257a5..5211ddf79633 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -444,13 +444,21 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)}; } } + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); + createVersionFile(environment, environment.defines); final Directory outputDirectory = environment.outputDir.childDirectory('assets'); outputDirectory.createSync(recursive: true); + final Depfile depfile = await copyAssets( environment, environment.outputDir.childDirectory('assets'), targetPlatform: TargetPlatform.web_javascript, + buildMode: buildMode, ); final DepfileService depfileService = environment.depFileService; depfileService.writeToFile( diff --git a/packages/flutter_tools/lib/src/build_system/targets/windows.dart b/packages/flutter_tools/lib/src/build_system/targets/windows.dart index ada0afe5b556..2aadcf12b179 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/windows.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/windows.dart @@ -142,6 +142,7 @@ abstract class BundleWindowsAssets extends Target { environment, outputDirectory, targetPlatform: targetPlatform, + buildMode: buildMode, ); environment.depFileService.writeToFile( depfile, diff --git a/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart b/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart index 8e352d2186e9..be56ea84ff91 100644 --- a/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart +++ b/packages/flutter_tools/lib/src/build_system/tools/asset_transformer.dart @@ -12,6 +12,7 @@ import '../../base/error_handling_io.dart'; import '../../base/file_system.dart'; import '../../base/io.dart'; import '../../base/logger.dart'; +import '../../build_info.dart'; import '../../devfs.dart'; import '../../flutter_manifest.dart'; import '../build_system.dart'; @@ -22,13 +23,18 @@ final class AssetTransformer { required ProcessManager processManager, required FileSystem fileSystem, required String dartBinaryPath, + required BuildMode buildMode, }) : _processManager = processManager, _fileSystem = fileSystem, - _dartBinaryPath = dartBinaryPath; + _dartBinaryPath = dartBinaryPath, + _buildMode = buildMode; + + static const String buildModeEnvVar = 'FLUTTER_BUILD_MODE'; final ProcessManager _processManager; final FileSystem _fileSystem; final String _dartBinaryPath; + final BuildMode _buildMode; /// The [Source] inputs that targets using this should depend on. /// @@ -46,6 +52,7 @@ final class AssetTransformer { required String outputPath, required String workingDirectory, required List transformerEntries, + required Logger logger, }) async { String getTempFilePath(int transformStep) { @@ -59,6 +66,7 @@ final class AssetTransformer { File tempOutputFile = _fileSystem.systemTempDirectory.childFile(getTempFilePath(1)); ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); + final Stopwatch stopwatch = Stopwatch()..start(); try { for (final (int i, AssetTransformerEntry transformer) in transformerEntries.indexed) { final AssetTransformationFailure? transformerFailure = await _applyTransformer( @@ -66,6 +74,7 @@ final class AssetTransformer { output: tempOutputFile, transformer: transformer, workingDirectory: workingDirectory, + logger: logger, ); if (transformerFailure != null) { @@ -85,6 +94,8 @@ final class AssetTransformer { ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); } } + + logger.printTrace("Finished transforming asset at path '${asset.path}' (${stopwatch.elapsedMilliseconds}ms)"); } finally { ErrorHandlingFileSystem.deleteIfExists(tempInputFile); ErrorHandlingFileSystem.deleteIfExists(tempOutputFile); @@ -98,6 +109,7 @@ final class AssetTransformer { required File output, required AssetTransformerEntry transformer, required String workingDirectory, + required Logger logger, }) async { final List transformerArguments = [ '--input=${asset.absolute.path}', @@ -112,9 +124,13 @@ final class AssetTransformer { ...transformerArguments, ]; + logger.printTrace("Transforming asset using command '${command.join(' ')}'"); final ProcessResult result = await _processManager.run( command, workingDirectory: workingDirectory, + environment: { + AssetTransformer.buildModeEnvVar: _buildMode.cliName, + } ); final String stdout = result.stdout as String; final String stderr = result.stderr as String; @@ -190,6 +206,7 @@ final class DevelopmentAssetTransformer { outputPath: output.path, transformerEntries: transformerEntries, workingDirectory: workingDirectory, + logger: _logger, ); if (failure != null) { _logger.printError(failure.message); diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index 81f3872a0f97..302d023ef50f 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -147,6 +147,7 @@ Future writeBundle( required Artifacts artifacts, required Logger logger, required Directory projectDir, + required BuildMode buildMode, }) async { if (bundleDir.existsSync()) { try { @@ -178,6 +179,7 @@ Future writeBundle( processManager: processManager, fileSystem: fileSystem, dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary), + buildMode: buildMode, ); // Limit number of open files to avoid running out of file descriptors. @@ -207,6 +209,7 @@ Future writeBundle( outputPath: file.path, workingDirectory: projectDir.path, transformerEntries: entry.value.transformers, + logger: logger, ); doCopy = false; if (failure != null) { diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 5ac969e9d860..56278362ab89 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -41,6 +41,9 @@ List _kDefaultTargets = [ const DebugMacOSBundleFlutterAssets(), const ProfileMacOSBundleFlutterAssets(), const ReleaseMacOSBundleFlutterAssets(), + const DebugUnpackMacOS(), + const ProfileUnpackMacOS(), + const ReleaseUnpackMacOS(), // Linux targets const DebugBundleLinuxAssets(TargetPlatform.linux_x64), const DebugBundleLinuxAssets(TargetPlatform.linux_arm64), @@ -72,6 +75,9 @@ List _kDefaultTargets = [ const DebugIosApplicationBundle(), const ProfileIosApplicationBundle(), const ReleaseIosApplicationBundle(), + const DebugUnpackIOS(), + const ProfileUnpackIOS(), + const ReleaseUnpackIOS(), // Windows targets const UnpackWindows(TargetPlatform.windows_x64), const UnpackWindows(TargetPlatform.windows_arm64), diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index acf34b7c0924..9530e580336f 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -23,6 +23,7 @@ import '../globals.dart' as globals; import '../ios/application_package.dart'; import '../ios/mac.dart'; import '../ios/plist_parser.dart'; +import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; import 'build.dart'; @@ -51,7 +52,7 @@ class BuildIOSCommand extends _BuildIOSSubCommand { final String name = 'ios'; @override - final String description = 'Build an iOS application bundle (macOS host only).'; + final String description = 'Build an iOS application bundle.'; @override final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.build; @@ -128,7 +129,7 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { final List aliases = ['xcarchive']; @override - final String description = 'Build an iOS archive bundle and IPA for distribution (macOS host only).'; + final String description = 'Build an iOS archive bundle and IPA for distribution.'; @override final XcodeBuildAction xcodeBuildAction = XcodeBuildAction.archive; @@ -686,7 +687,15 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { xcodeBuildResult = result; if (!result.success) { - await diagnoseXcodeBuildFailure(result, globals.flutterUsage, globals.logger, globals.analytics); + await diagnoseXcodeBuildFailure( + result, + analytics: globals.analytics, + fileSystem: globals.fs, + flutterUsage: globals.flutterUsage, + logger: globals.logger, + platform: SupportedPlatform.ios, + project: app.project.parent, + ); final String presentParticiple = xcodeBuildAction == XcodeBuildAction.build ? 'building' : 'archiving'; throwToolExit('Encountered error while $presentParticiple for $logTarget.'); } diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index e8e224e57392..1228758eb16f 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -272,7 +272,12 @@ class BuildIOSFrameworkCommand extends BuildFrameworkCommand { buildInfo, modeDirectory, iPhoneBuildOutput, simulatorBuildOutput); // Build and copy plugins. - await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode); + await processPodsIfNeeded( + project.ios, + getIosBuildDirectory(), + buildInfo.mode, + forceCocoaPodsOnly: true, + ); if (boolArg('plugins') && hasPlugins(project)) { await _producePlugins(buildInfo.mode, xcodeBuildConfiguration, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory); } diff --git a/packages/flutter_tools/lib/src/commands/build_macos_framework.dart b/packages/flutter_tools/lib/src/commands/build_macos_framework.dart index 30d1a705afe1..715e2446d15c 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos_framework.dart @@ -94,7 +94,12 @@ class BuildMacOSFrameworkCommand extends BuildFrameworkCommand { await _produceAppFramework(buildInfo, modeDirectory, buildOutput); // Build and copy plugins. - await processPodsIfNeeded(project.macos, getMacOSBuildDirectory(), buildInfo.mode); + await processPodsIfNeeded( + project.macos, + getMacOSBuildDirectory(), + buildInfo.mode, + forceCocoaPodsOnly: true, + ); if (boolArg('plugins') && hasPlugins(project)) { await _producePlugins(xcodeBuildConfiguration, buildOutput, modeDirectory); } diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 0b22f065c965..81ca66fadb3d 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -133,7 +133,8 @@ class ConfigCommand extends FlutterCommand { // TODO(eliasyishak): Set the telemetry for the unified_analytics // package as well, the above will be removed once we have - // fully transitioned to using the new package + // fully transitioned to using the new package, + // https://github.com/flutter/flutter/issues/128251 await globals.analytics.setTelemetry(value); } diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index e470fa222dce..6eed33fb657c 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -21,6 +21,8 @@ import '../flutter_manifest.dart'; import '../flutter_project_metadata.dart'; import '../globals.dart' as globals; import '../ios/code_signing.dart'; +import '../macos/swift_package_manager.dart'; +import '../macos/swift_packages.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; @@ -324,6 +326,7 @@ class CreateCommand extends CreateBase { projectDescription: stringArg('description'), flutterRoot: flutterRoot, withPlatformChannelPluginHook: generateMethodChannelsPlugin, + withSwiftPackageManager: featureFlags.isSwiftPackageManagerEnabled, withFfiPluginHook: generateFfiPlugin, withFfiPackage: generateFfiPackage, withEmptyMain: emptyArgument, @@ -603,8 +606,21 @@ Your $application code is in $relativeAppMain. ? stringArg('description') : 'A new Flutter plugin project.'; templateContext['description'] = description; + + final String? projectName = templateContext['projectName'] as String?; + final List templates = ['plugin', 'plugin_shared']; + if ((templateContext['ios'] == true || templateContext['macos'] == true) && featureFlags.isSwiftPackageManagerEnabled) { + templates.add('plugin_swift_package_manager'); + templateContext['swiftLibraryName'] = projectName?.replaceAll('_', '-'); + templateContext['swiftToolsVersion'] = minimumSwiftToolchainVersion; + templateContext['iosSupportedPlatform'] = SwiftPackageManager.iosSwiftPackageSupportedPlatform.format(); + templateContext['macosSupportedPlatform'] = SwiftPackageManager.macosSwiftPackageSupportedPlatform.format(); + } else { + templates.add('plugin_cocoapods'); + } + generatedCount += await renderMerged( - ['plugin', 'plugin_shared'], + templates, directory, templateContext, overwrite: overwrite, @@ -618,7 +634,6 @@ Your $application code is in $relativeAppMain. project: project, requireAndroidSdk: false); } - final String? projectName = templateContext['projectName'] as String?; final String organization = templateContext['organization']! as String; // Required to make the context. final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?; final String exampleProjectName = '${projectName}_example'; diff --git a/packages/flutter_tools/lib/src/commands/create_base.dart b/packages/flutter_tools/lib/src/commands/create_base.dart index 93c9050c9b08..17e70ce5a1ec 100644 --- a/packages/flutter_tools/lib/src/commands/create_base.dart +++ b/packages/flutter_tools/lib/src/commands/create_base.dart @@ -351,6 +351,7 @@ abstract class CreateBase extends FlutterCommand { String? kotlinVersion, String? gradleVersion, bool withPlatformChannelPluginHook = false, + bool withSwiftPackageManager = false, bool withFfiPluginHook = false, bool withFfiPackage = false, bool withEmptyMain = false, @@ -404,6 +405,7 @@ abstract class CreateBase extends FlutterCommand { 'withFfiPackage': withFfiPackage, 'withFfiPluginHook': withFfiPluginHook, 'withPlatformChannelPluginHook': withPlatformChannelPluginHook, + 'withSwiftPackageManager': withSwiftPackageManager, 'withPluginHook': withFfiPluginHook || withFfiPackage || withPlatformChannelPluginHook, 'withEmptyMain': withEmptyMain, 'androidLanguage': androidLanguage, @@ -667,8 +669,18 @@ abstract class CreateBase extends FlutterCommand { 'templates', 'template_manifest.json', ); + final String manifestFileContents; + try { + manifestFileContents = globals.fs.file(manifestPath).readAsStringSync(); + } on FileSystemException catch (e) { + throwToolExit( + 'Unable to read the template manifest at path "$manifestPath".\n' + 'Make sure that your user account has sufficient permissions to read this file.\n' + 'Exception details: $e', + ); + } final Map manifest = json.decode( - globals.fs.file(manifestPath).readAsStringSync(), + manifestFileContents, ) as Map; return Set.from( (manifest['files']! as List).cast().map( diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index b3cf67153213..ba505dfa5e10 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -411,7 +411,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { String? testAssetDirectory; if (buildTestAssets) { - await _buildTestAsset(flavor: buildInfo.flavor, impellerStatus: debuggingOptions.enableImpeller); + await _buildTestAsset( + flavor: buildInfo.flavor, + impellerStatus: debuggingOptions.enableImpeller, + buildMode: debuggingOptions.buildInfo.mode, + ); testAssetDirectory = globals.fs.path. join(flutterProject.directory.path, 'build', 'unit_test_assets'); } @@ -674,6 +678,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { Future _buildTestAsset({ required String? flavor, required ImpellerStatus impellerStatus, + required BuildMode buildMode, }) async { final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); final int build = await assetBundle.build( @@ -694,6 +699,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { artifacts: globals.artifacts!, logger: globals.logger, projectDir: globals.fs.currentDirectory, + buildMode: buildMode, ); } } diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 69767bb05ef1..dd0376aa06ee 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -21,6 +21,11 @@ import '../runner/flutter_command.dart'; import '../update_packages_pins.dart'; import '../version.dart'; +// Pub packages are rolled automatically by the flutter-pub-roller-bot +// by using the `flutter update-packages --force-upgrade`. +// For the latest status, see: +// https://github.com/pulls?q=author%3Aflutter-pub-roller-bot + class UpdatePackagesCommand extends FlutterCommand { UpdatePackagesCommand() { argParser @@ -32,7 +37,7 @@ class UpdatePackagesCommand extends FlutterCommand { ) ..addOption( 'cherry-pick-package', - help: 'Attempt to update only the specified package. The "-cherry-pick-version" version must be specified also.', + help: 'Attempt to update only the specified package. The "--cherry-pick-version" version must be specified also.', ) ..addOption( 'cherry-pick-version', @@ -64,7 +69,7 @@ class UpdatePackagesCommand extends FlutterCommand { 'consumer-only', help: 'Only prints the dependency graph that is the transitive closure ' 'that a consumer of the Flutter SDK will observe (when combined ' - 'with transitive-closure).', + 'with "--transitive-closure").', negatable: false, ) ..addFlag( diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index d9918e385efd..0bcf4439e3bd 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -221,7 +221,7 @@ class UpgradeCommandRunner { 'We do not recommend using this channel for normal use as it more likely to contain serious regressions.\n' '\n' 'For information on contributing to Flutter, see our contributing guide:\n' - ' https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md\n' + ' https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md\n' '\n' 'For the most up to date stable version of flutter, consider using the "beta" channel instead. ' 'The Flutter "beta" channel enjoys all the same automated testing as the "stable" channel, ' diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 2fe3b8ba3e9c..f1ce205dbfc5 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -247,12 +247,14 @@ class KernelCompiler { sdkRoot = '$sdkRoot/'; } String? mainUri; - final File mainFile = _fileSystem.file(mainPath); - final Uri mainFileUri = mainFile.uri; - if (packagesPath != null) { - mainUri = packageConfig.toPackageUri(mainFileUri)?.toString(); + if (mainPath != null) { + final File mainFile = _fileSystem.file(mainPath); + final Uri mainFileUri = mainFile.uri; + if (packagesPath != null) { + mainUri = packageConfig.toPackageUri(mainFileUri)?.toString(); + } + mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\'); } - mainUri ??= toMultiRootPath(mainFileUri, _fileSystemScheme, _fileSystemRoots, _fileSystem.path.separator == r'\'); if (outputFilePath != null && !_fileSystem.isFileSync(outputFilePath)) { _fileSystem.file(outputFilePath).createSync(recursive: true); } @@ -359,7 +361,8 @@ class KernelCompiler { // See: https://github.com/flutter/flutter/issues/103994 '--verbosity=error', ...?extraFrontEndOptions, - mainUri, + if (mainUri != null) mainUri + else '--native-assets-only', ]; _logger.printTrace(command.join(' ')); diff --git a/packages/flutter_tools/lib/src/dart/language_version.dart b/packages/flutter_tools/lib/src/dart/language_version.dart index 311ce2e992c9..de7bb7ffc224 100644 --- a/packages/flutter_tools/lib/src/dart/language_version.dart +++ b/packages/flutter_tools/lib/src/dart/language_version.dart @@ -47,7 +47,7 @@ bool _inUnitTest() { /// for language declarations other than library, part, or import. /// /// The specification for the language version tag is defined at: -/// https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/feature-specification.md#individual-library-language-version-override +/// https://github.com/dart-lang/language/blob/main/accepted/2.8/language-versioning/feature-specification.md#individual-library-language-version-override LanguageVersion determineLanguageVersion(File file, Package? package, String flutterRoot) { int blockCommentDepth = 0; // If reading the file fails, default to a null-safe version. The diff --git a/packages/flutter_tools/lib/src/debug_adapters/README.md b/packages/flutter_tools/lib/src/debug_adapters/README.md index 096a49d8efae..917a71434227 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/README.md +++ b/packages/flutter_tools/lib/src/debug_adapters/README.md @@ -2,7 +2,7 @@ This document is Flutter-specific. For information on the standard Dart DAP implementation, [see this document](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md). -Flutter includes support for debugging using [the Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) as an alternative to using the [VM Service](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md) directly, simplifying the integration for new editors. +Flutter includes support for debugging using [the Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) as an alternative to using the [VM Service](https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md) directly, simplifying the integration for new editors. The debug adapters are started with the `flutter debug-adapter` command and are intended to be consumed by DAP-compliant tools such as Flutter-specific extensions for editors, or configured by users whose editors include generic configurable DAP clients. diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 9db81a4df819..4027001eee3b 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -442,6 +442,7 @@ class DevFS { required FileSystem fileSystem, required ProcessManager processManager, required Artifacts artifacts, + required BuildMode buildMode, HttpClient? httpClient, Duration? uploadRetryThrottle, StopwatchFactory stopwatchFactory = const StopwatchFactory(), @@ -465,6 +466,7 @@ class DevFS { processManager: processManager, fileSystem: fileSystem, dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary), + buildMode: buildMode, ), fileSystem: fileSystem, logger: logger, diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 202af75d6ff2..cef3c7190447 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -453,7 +453,8 @@ class Doctor { doctorInvocationId: analyticsTimestamp, )); } - // TODO(eliasyishak): remove this after migrating from package:usage + // TODO(eliasyishak): remove this after migrating from package:usage, + // https://github.com/flutter/flutter/issues/128251 DoctorResultEvent(validator: validator, result: result).send(); } diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index fcc49731824d..7a8a209f5c78 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -51,6 +51,9 @@ abstract class FeatureFlags { /// Whether native assets compilation and bundling is enabled. bool get isPreviewDeviceEnabled => true; + /// Whether Swift Package Manager dependency management is enabled. + bool get isSwiftPackageManagerEnabled => false; + /// Whether a particular feature is enabled for the current channel. /// /// Prefer using one of the specific getters above instead of this API. @@ -70,6 +73,7 @@ const List allFeatures = [ cliAnimation, nativeAssets, previewDevice, + swiftPackageManager, ]; /// All current Flutter feature flags that can be configured. @@ -175,6 +179,16 @@ const Feature previewDevice = Feature( ), ); +/// Enable Swift Package Mangaer as a darwin dependency manager. +const Feature swiftPackageManager = Feature( + name: 'support for Swift Package Manager for iOS and macOS', + configSetting: 'enable-swift-package-manager', + environmentOverride: 'SWIFT_PACKAGE_MANAGER', + master: FeatureChannelSetting( + available: true, + ), +); + /// A [Feature] is a process for conditionally enabling tool features. /// /// All settings are optional, and if not provided will generally default to diff --git a/packages/flutter_tools/lib/src/flutter_features.dart b/packages/flutter_tools/lib/src/flutter_features.dart index f9e961c219a5..df5b5c3be8f7 100644 --- a/packages/flutter_tools/lib/src/flutter_features.dart +++ b/packages/flutter_tools/lib/src/flutter_features.dart @@ -58,6 +58,9 @@ class FlutterFeatureFlags implements FeatureFlags { @override bool get isPreviewDeviceEnabled => isEnabled(previewDevice); + @override + bool get isSwiftPackageManagerEnabled => isEnabled(swiftPackageManager); + @override bool isEnabled(Feature feature) { final String currentChannel = _flutterVersion.channel; diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index 736e00ba02ea..04455506570e 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart @@ -136,6 +136,12 @@ class FlutterManifest { return _flutterDescriptor['uses-material-design'] as bool? ?? false; } + /// If true, does not use Swift Package Manager as a dependency manager. + /// CocoaPods will be used instead. + bool get disabledSwiftPackageManager { + return _flutterDescriptor['disable-swift-package-manager'] as bool? ?? false; + } + /// True if this Flutter module should use AndroidX dependencies. /// /// If false the deprecated Android Support library will be used. @@ -547,6 +553,10 @@ void _validateFlutter(YamlMap? yaml, List errors) { break; case 'deferred-components': _validateDeferredComponents(kvp, errors); + case 'disable-swift-package-manager': + if (yamlValue is! bool) { + errors.add('Expected "$yamlKey" to be a bool, but got $yamlValue (${yamlValue.runtimeType}).'); + } default: errors.add('Unexpected child "$yamlKey" found under "flutter".'); break; diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index dc693fea2968..9e1090295b18 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -22,6 +22,8 @@ import 'dart/language_version.dart'; import 'dart/package_map.dart'; import 'features.dart'; import 'globals.dart' as globals; +import 'macos/darwin_dependency_management.dart'; +import 'macos/swift_package_manager.dart'; import 'platform_plugins.dart'; import 'plugins.dart'; import 'project.dart'; @@ -160,7 +162,11 @@ const String _kFlutterPluginsSharedDarwinSource = 'shared_darwin_source'; /// /// /// Finally, returns [true] if the plugins list has changed, otherwise returns [false]. -bool _writeFlutterPluginsList(FlutterProject project, List plugins) { +bool _writeFlutterPluginsList( + FlutterProject project, + List plugins, { + bool forceCocoaPodsOnly = false, +}) { final File pluginsFile = project.flutterPluginsDependenciesFile; if (plugins.isEmpty) { return ErrorHandlingFileSystem.deleteIfExists(pluginsFile); @@ -190,6 +196,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List plugins) { result['dependencyGraph'] = _createPluginLegacyDependencyGraph(plugins); result['date_created'] = globals.systemClock.now().toString(); result['version'] = globals.flutterVersion.frameworkVersion; + result['swift_package_manager_enabled'] = !forceCocoaPodsOnly && project.usesSwiftPackageManager; // Only notify if the plugins list has changed. [date_created] will always be different, // [version] is not relevant for this check. @@ -1000,6 +1007,7 @@ Future refreshPluginsList( FlutterProject project, { bool iosPlatform = false, bool macOSPlatform = false, + bool forceCocoaPodsOnly = false, }) async { final List plugins = await findPlugins(project); // Sort the plugins by name to keep ordering stable in generated files. @@ -1008,8 +1016,12 @@ Future refreshPluginsList( // Write the legacy plugin files to avoid breaking existing apps. final bool legacyChanged = _writeFlutterPluginsListLegacy(project, plugins); - final bool changed = _writeFlutterPluginsList(project, plugins); - if (changed || legacyChanged) { + final bool changed = _writeFlutterPluginsList( + project, + plugins, + forceCocoaPodsOnly: forceCocoaPodsOnly, + ); + if (changed || legacyChanged || forceCocoaPodsOnly) { createPluginSymlinks(project, force: true); if (iosPlatform) { globals.cocoaPods?.invalidatePodInstallOutput(project.ios); @@ -1069,6 +1081,7 @@ Future injectPlugins( bool macOSPlatform = false, bool windowsPlatform = false, Iterable? allowedPlugins, + DarwinDependencyManagement? darwinDependencyManagement, }) async { final List plugins = await findPlugins(project); // Sort the plugins by name to keep ordering stable in generated files. @@ -1088,20 +1101,27 @@ Future injectPlugins( if (windowsPlatform) { await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins); } - if (!project.isModule) { - final List darwinProjects = [ - if (iosPlatform) project.ios, - if (macOSPlatform) project.macos, - ]; - for (final XcodeBasedProject subproject in darwinProjects) { - if (plugins.isNotEmpty) { - await globals.cocoaPods?.setupPodfile(subproject); - } - /// The user may have a custom maintained Podfile that they're running `pod install` - /// on themselves. - else if (subproject.podfile.existsSync() && subproject.podfileLock.existsSync()) { - globals.cocoaPods?.addPodsDependencyToFlutterXcconfig(subproject); - } + if (iosPlatform || macOSPlatform) { + final DarwinDependencyManagement darwinDependencyManagerSetup = darwinDependencyManagement ?? DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: globals.cocoaPods!, + swiftPackageManager: SwiftPackageManager( + fileSystem: globals.fs, + templateRenderer: globals.templateRenderer, + ), + fileSystem: globals.fs, + logger: globals.logger, + ); + if (iosPlatform) { + await darwinDependencyManagerSetup.setUp( + platform: SupportedPlatform.ios, + ); + } + if (macOSPlatform) { + await darwinDependencyManagerSetup.setUp( + platform: SupportedPlatform.macos, + ); } } } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index ad462a76ef07..650f495aff4c 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -496,7 +496,15 @@ class IOSDevice extends Device { ); if (!buildResult.success) { _logger.printError('Could not build the precompiled application for the device.'); - await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, _logger, globals.analytics); + await diagnoseXcodeBuildFailure( + buildResult, + analytics: globals.analytics, + fileSystem: globals.fs, + flutterUsage: globals.flutterUsage, + logger: globals.logger, + platform: SupportedPlatform.ios, + project: package.project.parent, + ); _logger.printError(''); return LaunchResult.failed(); } diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 454e4dc87e2a..acbbf4e9f62c 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -19,12 +19,16 @@ import '../build_info.dart'; import '../cache.dart'; import '../device.dart'; import '../flutter_manifest.dart'; +import '../flutter_plugins.dart'; import '../globals.dart' as globals; import '../macos/cocoapod_utils.dart'; +import '../macos/swift_package_manager.dart'; import '../macos/xcode.dart'; +import '../migrations/swift_package_manager_integration_migration.dart'; import '../migrations/xcode_project_object_version_migration.dart'; import '../migrations/xcode_script_build_phase_migration.dart'; import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; +import '../plugins.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import 'application_package.dart'; @@ -35,6 +39,7 @@ import 'migrations/project_base_configuration_migration.dart'; import 'migrations/project_build_location_migration.dart'; import 'migrations/remove_bitcode_migration.dart'; import 'migrations/remove_framework_link_and_embedding_migration.dart'; +import 'migrations/uiapplicationmain_deprecation_migration.dart'; import 'migrations/xcode_build_system_migration.dart'; import 'xcode_build_settings.dart'; import 'xcodeproj.dart'; @@ -147,6 +152,8 @@ Future buildXcodeProject({ return XcodeBuildResult(success: false); } + final FlutterProject project = FlutterProject.current(); + final List migrators = [ RemoveFrameworkLinkAndEmbeddingMigration(app.project, globals.logger, globals.flutterUsage, globals.analytics), XcodeBuildSystemMigration(app.project, globals.logger), @@ -158,6 +165,17 @@ Future buildXcodeProject({ XcodeScriptBuildPhaseMigration(app.project, globals.logger), RemoveBitcodeMigration(app.project, globals.logger), XcodeThinBinaryBuildPhaseInputPathsMigration(app.project, globals.logger), + UIApplicationMainDeprecationMigration(app.project, globals.logger), + if (project.usesSwiftPackageManager && app.project.flutterPluginSwiftPackageManifest.existsSync()) + SwiftPackageManagerIntegrationMigration( + app.project, + SupportedPlatform.ios, + buildInfo, + xcodeProjectInterpreter: globals.xcodeProjectInterpreter!, + logger: globals.logger, + fileSystem: globals.fs, + plistParser: globals.plistParser, + ), ]; final ProjectMigration migration = ProjectMigration(migrators); @@ -243,12 +261,21 @@ Future buildXcodeProject({ ); } - final FlutterProject project = FlutterProject.current(); await updateGeneratedXcodeProperties( project: project, targetOverride: targetOverride, buildInfo: buildInfo, ); + if (project.usesSwiftPackageManager) { + final String? iosDeploymentTarget = buildSettings['IPHONEOS_DEPLOYMENT_TARGET']; + if (iosDeploymentTarget != null) { + SwiftPackageManager.updateMinimumDeployment( + platform: SupportedPlatform.ios, + project: project.ios, + deploymentTarget: iosDeploymentTarget, + ); + } + } await processPodsIfNeeded(project.ios, getIosBuildDirectory(), buildInfo.mode); if (configOnly) { return XcodeBuildResult(success: true); @@ -594,11 +621,14 @@ return result.exitCode != 0 && } Future diagnoseXcodeBuildFailure( - XcodeBuildResult result, - Usage flutterUsage, - Logger logger, - Analytics analytics, -) async { + XcodeBuildResult result, { + required Analytics analytics, + required Logger logger, + required FileSystem fileSystem, + required Usage flutterUsage, + required SupportedPlatform platform, + required FlutterProject project, +}) async { final XcodeBuildExecution? xcodeBuildExecution = result.xcodeBuildExecution; if (xcodeBuildExecution != null && xcodeBuildExecution.environmentType == EnvironmentType.physical @@ -625,7 +655,14 @@ Future diagnoseXcodeBuildFailure( } // Handle errors. - final bool issueDetected = _handleIssues(result.xcResult, logger, xcodeBuildExecution); + final bool issueDetected = await _handleIssues( + result, + xcodeBuildExecution, + project: project, + platform: platform, + logger: logger, + fileSystem: fileSystem, + ); if (!issueDetected && xcodeBuildExecution != null) { // Fallback to use stdout to detect and print issues. @@ -726,7 +763,11 @@ bool upgradePbxProjWithFlutterAssets(IosProject project, Logger logger) { return true; } -_XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue, required Logger logger}) { +_XCResultIssueHandlingResult _handleXCResultIssue({ + required XCResultIssue issue, + required XcodeBuildResult result, + required Logger logger, +}) { // Issue summary from xcresult. final StringBuffer issueSummaryBuffer = StringBuffer(); issueSummaryBuffer.write(issue.subType ?? 'Unknown'); @@ -759,20 +800,61 @@ _XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue, if (missingPlatform != null) { return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false, missingPlatform: missingPlatform); } + } else if (message.toLowerCase().contains('redefinition of module')) { + final String? duplicateModule = _parseModuleRedefinition(message); + return _XCResultIssueHandlingResult( + requiresProvisioningProfile: false, + hasProvisioningProfileIssue: false, + duplicateModule: duplicateModule, + ); + } else if (message.toLowerCase().contains('duplicate symbols')) { + // The message does not contain the plugin name, must parse the stdout. + String? duplicateModule; + if (result.stdout != null) { + duplicateModule = _parseDuplicateSymbols(result.stdout!); + } + return _XCResultIssueHandlingResult( + requiresProvisioningProfile: false, + hasProvisioningProfileIssue: false, + duplicateModule: duplicateModule, + ); + } else if (message.toLowerCase().contains('not found')) { + final String? missingModule = _parseMissingModule(message); + if (missingModule != null) { + return _XCResultIssueHandlingResult( + requiresProvisioningProfile: false, + hasProvisioningProfileIssue: false, + missingModule: missingModule, + ); + } } return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false); } // Returns `true` if at least one issue is detected. -bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcodeBuildExecution) { +Future _handleIssues( + XcodeBuildResult result, + XcodeBuildExecution? xcodeBuildExecution, { + required FlutterProject project, + required SupportedPlatform platform, + required Logger logger, + required FileSystem fileSystem, +}) async { bool requiresProvisioningProfile = false; bool hasProvisioningProfileIssue = false; bool issueDetected = false; String? missingPlatform; + final List duplicateModules = []; + final List missingModules = []; + final XCResult? xcResult = result.xcResult; if (xcResult != null && xcResult.parseSuccess) { for (final XCResultIssue issue in xcResult.issues) { - final _XCResultIssueHandlingResult handlingResult = _handleXCResultIssue(issue: issue, logger: logger); + final _XCResultIssueHandlingResult handlingResult = _handleXCResultIssue( + issue: issue, + result: result, + logger: logger, + ); if (handlingResult.hasProvisioningProfileIssue) { hasProvisioningProfileIssue = true; } @@ -780,12 +862,20 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode requiresProvisioningProfile = true; } missingPlatform = handlingResult.missingPlatform; + if (handlingResult.duplicateModule != null) { + duplicateModules.add(handlingResult.duplicateModule!); + } + if (handlingResult.missingModule != null) { + missingModules.add(handlingResult.missingModule!); + } issueDetected = true; } } else if (xcResult != null) { globals.printTrace('XCResult parsing error: ${xcResult.parsingErrorMessage}'); } + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; + if (requiresProvisioningProfile) { logger.printError(noProvisioningProfileInstruction, emphasis: true); } else if ((!issueDetected || hasProvisioningProfileIssue) && _missingDevelopmentTeam(xcodeBuildExecution)) { @@ -801,11 +891,84 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode logger.printError("Also try selecting 'Product > Build' to fix the problem."); } else if (missingPlatform != null) { logger.printError(missingPlatformInstructions(missingPlatform), emphasis: true); + } else if (duplicateModules.isNotEmpty) { + final bool usesCocoapods = xcodeProject.podfile.existsSync(); + final bool usesSwiftPackageManager = project.usesSwiftPackageManager; + if (usesCocoapods && usesSwiftPackageManager) { + logger.printError( + 'Your project uses both CocoaPods and Swift Package Manager, which can ' + 'cause the above error. It may be caused by there being both a CocoaPod ' + 'and Swift Package Manager dependency for the following module(s): ' + '${duplicateModules.join(', ')}.\n\n' + 'You can try to identify which Pod the conflicting module is from by ' + 'looking at your "ios/Podfile.lock" dependency tree and requesting the ' + 'author add Swift Package Manager compatibility. See https://stackoverflow.com/a/27955017 ' + 'to learn more about understanding Podlock dependency tree. \n\n' + 'You can also disable Swift Package Manager for the project by adding the ' + 'following in the project\'s pubspec.yaml under the "flutter" section:\n' + ' "disable-swift-package-manager: true"\n', + ); + } + } else if (missingModules.isNotEmpty) { + final bool usesCocoapods = xcodeProject.podfile.existsSync(); + final bool usesSwiftPackageManager = project.usesSwiftPackageManager; + if (usesCocoapods && !usesSwiftPackageManager) { + final List swiftPackageOnlyPlugins = []; + for (final String module in missingModules) { + if (await _isPluginSwiftPackageOnly( + platform: platform, + project: project, + pluginName: module, + fileSystem: fileSystem, + )) { + swiftPackageOnlyPlugins.add(module); + } + } + if (swiftPackageOnlyPlugins.isNotEmpty) { + logger.printError( + 'Your project uses CocoaPods as a dependency manager, but the following ' + 'plugin(s) only support Swift Package Manager: ${swiftPackageOnlyPlugins.join(', ')}.\n' + 'Try enabling Swift Package Manager with "flutter config --enable-swift-package-manager".', + ); + } + } } - return issueDetected; } +/// Returns true if a Package.swift is found for the plugin and a podspec is not. +Future _isPluginSwiftPackageOnly({ + required SupportedPlatform platform, + required FlutterProject project, + required String pluginName, + required FileSystem fileSystem, +}) async { + final List plugins = await findPlugins(project); + final Plugin? matched = plugins + .where((Plugin plugin) => + plugin.name.toLowerCase() == pluginName.toLowerCase() && + plugin.platforms[platform.name] != null) + .firstOrNull; + if (matched == null) { + return false; + } + final String? swiftPackagePath = matched.pluginSwiftPackageManifestPath( + fileSystem, + platform.name, + ); + final bool swiftPackageExists = swiftPackagePath != null && + fileSystem.file(swiftPackagePath).existsSync(); + + final String? podspecPath = matched.pluginPodspecPath( + fileSystem, + platform.name, + ); + final bool podspecExists = podspecPath != null && + fileSystem.file(podspecPath).existsSync(); + + return swiftPackageExists && !podspecExists; +} + // Return 'true' a missing development team issue is detected. bool _missingDevelopmentTeam(XcodeBuildExecution? xcodeBuildExecution) { // Make sure the user has specified one of: @@ -850,22 +1013,68 @@ String? _parseMissingPlatform(String message) { return pattern.firstMatch(message)?.group(1); } +String? _parseModuleRedefinition(String message) { + // Example: "Redefinition of module 'plugin_1_name'" + final RegExp pattern = RegExp(r"Redefinition of module '(.*?)'"); + final RegExpMatch? match = pattern.firstMatch(message); + if (match != null && match.groupCount > 0) { + final String? version = match.group(1); + return version; + } + return null; +} + +String? _parseDuplicateSymbols(String message) { + // Example: "duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in: + // /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o) + final RegExp pattern = RegExp(r'duplicate symbol [\s|\S]*?\/(.*)\.o'); + final RegExpMatch? match = pattern.firstMatch(message); + if (match != null && match.groupCount > 0) { + final String? version = match.group(1); + if (version != null) { + return version.split('/').last.split('[').first.split('(').first; + } + return version; + } + return null; +} + +String? _parseMissingModule(String message) { + // Example: "Module 'plugin_1_name' not found" + final RegExp pattern = RegExp(r"Module '(.*?)' not found"); + final RegExpMatch? match = pattern.firstMatch(message); + if (match != null && match.groupCount > 0) { + final String? version = match.group(1); + return version; + } + return null; +} + // The result of [_handleXCResultIssue]. class _XCResultIssueHandlingResult { - _XCResultIssueHandlingResult({ required this.requiresProvisioningProfile, required this.hasProvisioningProfileIssue, this.missingPlatform, + this.duplicateModule, + this.missingModule, }); - // An issue indicates that user didn't provide the provisioning profile. + /// An issue indicates that user didn't provide the provisioning profile. final bool requiresProvisioningProfile; - // An issue indicates that there is a provisioning profile issue. + /// An issue indicates that there is a provisioning profile issue. final bool hasProvisioningProfileIssue; final String? missingPlatform; + + /// An issue indicates a module is declared twice, potentially due to being + /// used in both Swift Package Manager and CocoaPods. + final String? duplicateModule; + + /// An issue indicates a module was imported but not found, potentially due + /// to it being Swift Package Manager compatible only. + final String? missingModule; } const String _kResultBundlePath = 'temporary_xcresult_bundle'; diff --git a/packages/flutter_tools/lib/src/ios/migrations/remove_bitcode_migration.dart b/packages/flutter_tools/lib/src/ios/migrations/remove_bitcode_migration.dart index 23b157a6cfac..34d9adc5c4ac 100644 --- a/packages/flutter_tools/lib/src/ios/migrations/remove_bitcode_migration.dart +++ b/packages/flutter_tools/lib/src/ios/migrations/remove_bitcode_migration.dart @@ -16,14 +16,12 @@ class RemoveBitcodeMigration extends ProjectMigrator { final File _xcodeProjectInfoFile; @override - Future migrate() async { + Future migrate() async { if (_xcodeProjectInfoFile.existsSync()) { processFileLines(_xcodeProjectInfoFile); } else { logger.printTrace('Xcode project not found, skipping removing bitcode migration.'); } - - return true; } @override diff --git a/packages/flutter_tools/lib/src/ios/migrations/uiapplicationmain_deprecation_migration.dart b/packages/flutter_tools/lib/src/ios/migrations/uiapplicationmain_deprecation_migration.dart new file mode 100644 index 000000000000..aca45398a58a --- /dev/null +++ b/packages/flutter_tools/lib/src/ios/migrations/uiapplicationmain_deprecation_migration.dart @@ -0,0 +1,51 @@ +// 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 '../../base/file_system.dart'; +import '../../base/project_migrator.dart'; +import '../../xcode_project.dart'; + +const String _appDelegateFileBefore = r''' +@UIApplicationMain +@objc class AppDelegate'''; + +const String _appDelegateFileAfter = r''' +@main +@objc class AppDelegate'''; + +/// Replace the deprecated `@UIApplicationMain` attribute with `@main`. +/// +/// See: +/// https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md +class UIApplicationMainDeprecationMigration extends ProjectMigrator { + UIApplicationMainDeprecationMigration( + IosProject project, + super.logger, + ) : _appDelegateSwift = project.appDelegateSwift; + + final File _appDelegateSwift; + + @override + Future migrate() async { + // Skip this migration if the project uses Objective-C. + if (!_appDelegateSwift.existsSync()) { + logger.printTrace( + 'ios/Runner/AppDelegate.swift not found, skipping @main migration.', + ); + return; + } + + // Migrate the ios/Runner/AppDelegate.swift file. + final String original = _appDelegateSwift.readAsStringSync(); + final String migrated = original.replaceFirst(_appDelegateFileBefore, _appDelegateFileAfter); + if (original == migrated) { + return; + } + + logger.printWarning( + 'ios/Runner/AppDelegate.swift uses the deprecated @UIApplicationMain attribute, updating.', + ); + _appDelegateSwift.writeAsStringSync(migrated); + } +} diff --git a/packages/flutter_tools/lib/src/ios/plist_parser.dart b/packages/flutter_tools/lib/src/ios/plist_parser.dart index 7f7f4cc90c7b..8117a3c73a6b 100644 --- a/packages/flutter_tools/lib/src/ios/plist_parser.dart +++ b/packages/flutter_tools/lib/src/ios/plist_parser.dart @@ -64,6 +64,35 @@ class PlistParser { } } + /// Returns the content, converted to JSON, of the plist file located at + /// [filePath]. + /// + /// If [filePath] points to a non-existent file or a file that's not a + /// valid property list file, this will return null. + String? plistJsonContent(String filePath) { + if (!_fileSystem.isFileSync(_plutilExecutable)) { + throw const FileNotFoundException(_plutilExecutable); + } + final List args = [ + _plutilExecutable, + '-convert', + 'json', + '-o', + '-', + filePath, + ]; + try { + final String jsonContent = _processUtils.runSync( + args, + throwOnError: true, + ).stdout.trim(); + return jsonContent; + } on ProcessException catch (error) { + _logger.printError('$error'); + return null; + } + } + /// Replaces the string key in the given plist file with the given value. /// /// If the value is null, then the key will be removed. diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 9a1d0cf4e342..fe6b1398f57d 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -571,7 +571,15 @@ class IOSSimulator extends Device { deviceID: id, ); if (!buildResult.success) { - await diagnoseXcodeBuildFailure(buildResult, globals.flutterUsage, globals.logger, globals.analytics); + await diagnoseXcodeBuildFailure( + buildResult, + analytics: globals.analytics, + fileSystem: globals.fs, + flutterUsage: globals.flutterUsage, + logger: globals.logger, + platform: SupportedPlatform.ios, + project: app.project.parent, + ); throwToolExit('Could not build the application for the simulator.'); } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 656c5f17295f..d4c94e0cfaf0 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -1003,6 +1003,7 @@ class WebDevFS implements DevFS { artifacts: globals.artifacts!, logger: globals.logger, projectDir: rootDirectory, + buildMode: buildInfo.mode, ); } } diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index dbbff107c8d1..575b60d5c1f3 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -16,6 +16,7 @@ import '../convert.dart'; import '../globals.dart' as globals; import '../ios/xcode_build_settings.dart'; import '../ios/xcodeproj.dart'; +import '../migrations/swift_package_manager_integration_migration.dart'; import '../migrations/xcode_project_object_version_migration.dart'; import '../migrations/xcode_script_build_phase_migration.dart'; import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; @@ -24,6 +25,7 @@ import 'application_package.dart'; import 'cocoapod_utils.dart'; import 'migrations/flutter_application_migration.dart'; import 'migrations/macos_deployment_target_migration.dart'; +import 'migrations/nsapplicationmain_deprecation_migration.dart'; import 'migrations/remove_macos_framework_link_and_embedding_migration.dart'; /// When run in -quiet mode, Xcode should only print from the underlying tasks to stdout. @@ -83,6 +85,17 @@ Future buildMacOS({ XcodeScriptBuildPhaseMigration(flutterProject.macos, globals.logger), XcodeThinBinaryBuildPhaseInputPathsMigration(flutterProject.macos, globals.logger), FlutterApplicationMigration(flutterProject.macos, globals.logger), + NSApplicationMainDeprecationMigration(flutterProject.macos, globals.logger), + if (flutterProject.usesSwiftPackageManager && flutterProject.macos.flutterPluginSwiftPackageManifest.existsSync()) + SwiftPackageManagerIntegrationMigration( + flutterProject.macos, + SupportedPlatform.macos, + buildInfo, + xcodeProjectInterpreter: globals.xcodeProjectInterpreter!, + logger: globals.logger, + fileSystem: globals.fs, + plistParser: globals.plistParser, + ), ]; final ProjectMigration migration = ProjectMigration(migrators); @@ -99,6 +112,11 @@ Future buildMacOS({ targetOverride: targetOverride, useMacOSConfig: true, ); + + // TODO(vashworth): Call `SwiftPackageManager.updateMinimumDeployment` + // using MACOSX_DEPLOYMENT_TARGET once https://github.com/flutter/flutter/issues/146204 + // is fixed. + await processPodsIfNeeded(flutterProject.macos, getMacOSBuildDirectory(), buildInfo.mode); // If the xcfilelists do not exist, create empty version. if (!flutterProject.macos.inputFileList.existsSync()) { diff --git a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart index 7b6213eb2e18..eab5413263cb 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapod_utils.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import '../base/error_handling_io.dart'; import '../base/fingerprint.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -14,20 +15,57 @@ import '../project.dart'; Future processPodsIfNeeded( XcodeBasedProject xcodeProject, String buildDirectory, - BuildMode buildMode) async { + BuildMode buildMode, { + bool forceCocoaPodsOnly = false, +}) async { final FlutterProject project = xcodeProject.parent; + + // When using Swift Package Manager, the Podfile may not exist so if there + // isn't a Podfile, skip processing pods. + if (project.usesSwiftPackageManager && !xcodeProject.podfile.existsSync() && !forceCocoaPodsOnly) { + return; + } // Ensure that the plugin list is up to date, since hasPlugins relies on it. - await refreshPluginsList(project, macOSPlatform: project.macos.existsSync()); - if (!(hasPlugins(project) || (project.isModule && xcodeProject.podfile.existsSync()))) { + await refreshPluginsList( + project, + iosPlatform: project.ios.existsSync(), + macOSPlatform: project.macos.existsSync(), + forceCocoaPodsOnly: forceCocoaPodsOnly, + ); + + // If there are no plugins and if the project is a not module with an existing + // podfile, skip processing pods + if (!hasPlugins(project) && !(project.isModule && xcodeProject.podfile.existsSync())) { return; } - // If the Xcode project, Podfile, or generated xcconfig have changed since - // last run, pods should be updated. + + // If forcing the use of only CocoaPods, but the project is using Swift + // Package Manager, print a warning that CocoaPods will be used. + if (forceCocoaPodsOnly && project.usesSwiftPackageManager) { + globals.logger.printWarning( + 'Swift Package Manager does not yet support this command. ' + 'CocoaPods will be used instead.'); + + // If CocoaPods has been deintegrated, add it back. + if (!xcodeProject.podfile.existsSync()) { + await globals.cocoaPods?.setupPodfile(xcodeProject); + } + + // Delete Swift Package Manager manifest to invalidate fingerprinter + ErrorHandlingFileSystem.deleteIfExists( + xcodeProject.flutterPluginSwiftPackageManifest, + ); + } + + // If the Xcode project, Podfile, generated plugin Swift Package, or podhelper + // have changed since last run, pods should be updated. final Fingerprinter fingerprinter = Fingerprinter( fingerprintPath: globals.fs.path.join(buildDirectory, 'pod_inputs.fingerprint'), paths: [ xcodeProject.xcodeProjectInfoFile.path, xcodeProject.podfile.path, + if (xcodeProject.flutterPluginSwiftPackageManifest.existsSync()) + xcodeProject.flutterPluginSwiftPackageManifest.path, globals.fs.path.join( Cache.flutterRoot!, 'packages', diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 1c4517e498e1..e75bfe44837f 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -21,11 +21,11 @@ import '../cache.dart'; import '../ios/xcodeproj.dart'; import '../migrations/cocoapods_script_symlink.dart'; import '../migrations/cocoapods_toolchain_directory_migration.dart'; +import '../project.dart'; import '../reporting/reporting.dart'; -import '../xcode_project.dart'; const String noCocoaPodsConsequence = ''' - CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. + CocoaPods is a package manager for iOS or macOS platform code. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins'''; @@ -47,9 +47,9 @@ const String outOfDatePluginsPodfileConsequence = ''' See https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms for details. If you have local Podfile edits you would like to keep, see https://github.com/flutter/flutter/issues/45197 for instructions.'''; -const String cocoaPodsInstallInstructions = 'see https://guides.cocoapods.org/using/getting-started.html#installation for instructions.'; +const String cocoaPodsInstallInstructions = 'see https://guides.cocoapods.org/using/getting-started.html#installation'; -const String cocoaPodsUpdateInstructions = 'see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods for instructions.'; +const String cocoaPodsUpdateInstructions = 'see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods'; const String podfileIosMigrationInstructions = ''' rm ios/Podfile'''; @@ -166,6 +166,10 @@ class CocoaPods { bool dependenciesChanged = true, }) async { if (!xcodeProject.podfile.existsSync()) { + // Swift Package Manager doesn't need Podfile, so don't error. + if (xcodeProject.parent.usesSwiftPackageManager) { + return false; + } throwToolExit('Podfile missing'); } _warnIfPodfileOutOfDate(xcodeProject); @@ -200,7 +204,7 @@ class CocoaPods { _logger.printWarning( 'Warning: CocoaPods not installed. Skipping pod install.\n' '$noCocoaPodsConsequence\n' - 'To install $cocoaPodsInstallInstructions\n', + 'For installation instructions, $cocoaPodsInstallInstructions\n', emphasis: true, ); return false; @@ -208,7 +212,7 @@ class CocoaPods { _logger.printWarning( 'Warning: CocoaPods is installed but broken. Skipping pod install.\n' '$brokenCocoaPodsConsequence\n' - 'To re-install $cocoaPodsInstallInstructions\n', + 'For re-installation instructions, $cocoaPodsInstallInstructions\n', emphasis: true, ); return false; @@ -216,14 +220,14 @@ class CocoaPods { _logger.printWarning( 'Warning: Unknown CocoaPods version installed.\n' '$unknownCocoaPodsConsequence\n' - 'To upgrade $cocoaPodsInstallInstructions\n', + 'To update CocoaPods, $cocoaPodsUpdateInstructions\n', emphasis: true, ); case CocoaPodsStatus.belowMinimumVersion: _logger.printWarning( 'Warning: CocoaPods minimum required version $cocoaPodsMinimumVersion or greater not installed. Skipping pod install.\n' '$noCocoaPodsConsequence\n' - 'To upgrade $cocoaPodsInstallInstructions\n', + 'To update CocoaPods, $cocoaPodsUpdateInstructions\n', emphasis: true, ); return false; @@ -231,7 +235,7 @@ class CocoaPods { _logger.printWarning( 'Warning: CocoaPods recommended version $cocoaPodsRecommendedVersion or greater not installed.\n' 'Pods handling may fail on some projects involving plugins.\n' - 'To upgrade $cocoaPodsInstallInstructions\n', + 'To update CocoaPods, $cocoaPodsUpdateInstructions\n', emphasis: true, ); case CocoaPodsStatus.recommended: @@ -258,6 +262,18 @@ class CocoaPods { addPodsDependencyToFlutterXcconfig(xcodeProject); return; } + final File podfileTemplate = await getPodfileTemplate( + xcodeProject, + runnerProject, + ); + podfileTemplate.copySync(podfile.path); + addPodsDependencyToFlutterXcconfig(xcodeProject); + } + + Future getPodfileTemplate( + XcodeBasedProject xcodeProject, + Directory runnerProject, + ) async { String podfileTemplateName; if (xcodeProject is MacOSProject) { podfileTemplateName = 'Podfile-macos'; @@ -268,7 +284,7 @@ class CocoaPods { )).containsKey('SWIFT_VERSION'); podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc'; } - final File podfileTemplate = _fileSystem.file(_fileSystem.path.join( + return _fileSystem.file(_fileSystem.path.join( Cache.flutterRoot!, 'packages', 'flutter_tools', @@ -276,8 +292,6 @@ class CocoaPods { 'cocoapods', podfileTemplateName, )); - podfileTemplate.copySync(podfile.path); - addPodsDependencyToFlutterXcconfig(xcodeProject); } /// Ensures all `Flutter/Xxx.xcconfig` files for the given Xcode-based @@ -287,12 +301,24 @@ class CocoaPods { _addPodsDependencyToFlutterXcconfig(xcodeProject, 'Release'); } + String includePodsXcconfig(String mode) { + return 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode + .toLowerCase()}.xcconfig'; + } + + bool xcconfigIncludesPods(File xcodeConfig) { + if (xcodeConfig.existsSync()) { + final String content = xcodeConfig.readAsStringSync(); + return content.contains('Pods/Target Support Files/Pods-'); + } + return false; + } + void _addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject, String mode) { final File file = xcodeProject.xcodeConfigFor(mode); if (file.existsSync()) { final String content = file.readAsStringSync(); - final String includeFile = 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode - .toLowerCase()}.xcconfig'; + final String includeFile = includePodsXcconfig(mode); final String include = '#include? "$includeFile"'; if (!content.contains('Pods/Target Support Files/Pods-')) { file.writeAsStringSync('$include\n$content', flush: true); diff --git a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart index 93fccd62e5ab..49c0dc4bf9f6 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods_validator.dart @@ -43,7 +43,7 @@ class CocoaPodsValidator extends DoctorValidator { case CocoaPodsStatus.unknownVersion: status = ValidationType.partial; messages.add(ValidationMessage.hint( - _userMessages.cocoaPodsUnknownVersion(unknownCocoaPodsConsequence, cocoaPodsInstallInstructions))); + _userMessages.cocoaPodsUnknownVersion(unknownCocoaPodsConsequence, cocoaPodsUpdateInstructions))); case CocoaPodsStatus.belowMinimumVersion: case CocoaPodsStatus.belowRecommendedVersion: status = ValidationType.partial; diff --git a/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart new file mode 100644 index 000000000000..af1cebe54ed4 --- /dev/null +++ b/packages/flutter_tools/lib/src/macos/darwin_dependency_management.dart @@ -0,0 +1,257 @@ +// 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 '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/logger.dart'; +import '../plugins.dart'; +import '../project.dart'; +import 'cocoapods.dart'; +import 'swift_package_manager.dart'; + +/// Flutter has two dependency management solutions for iOS and macOS +/// applications: CocoaPods and Swift Package Manager. They may be used +/// individually or together. This class handles setting up required files and +/// project settings for the dependency manager(s) being used. +class DarwinDependencyManagement { + DarwinDependencyManagement({ + required FlutterProject project, + required List plugins, + required CocoaPods cocoapods, + required SwiftPackageManager swiftPackageManager, + required FileSystem fileSystem, + required Logger logger, + }) : _project = project, + _plugins = plugins, + _cocoapods = cocoapods, + _swiftPackageManager = swiftPackageManager, + _fileSystem = fileSystem, + _logger = logger; + + final FlutterProject _project; + final List _plugins; + final CocoaPods _cocoapods; + final SwiftPackageManager _swiftPackageManager; + final FileSystem _fileSystem; + final Logger _logger; + + /// Generates/updates required files and project settings for Darwin + /// Dependency Managers (CocoaPods and Swift Package Manager). Projects may + /// use only CocoaPods (if no SPM compatible dependencies or SPM has been + /// disabled), only Swift Package Manager (if no CocoaPod dependencies), or + /// both. This only generates files for the manager(s) being used. + /// + /// CocoaPods requires a Podfile and certain values in the Flutter xcconfig + /// files. + /// + /// Swift Package Manager requires a generated Package.swift and certain + /// settings in the Xcode project's project.pbxproj and xcscheme (done later + /// before build). + Future setUp({ + required SupportedPlatform platform, + }) async { + if (platform != SupportedPlatform.ios && + platform != SupportedPlatform.macos) { + throwToolExit( + 'The platform ${platform.name} is incompatible with Darwin Dependency Managers. Only iOS and macOS are allowed.', + ); + } + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios + ? _project.ios + : _project.macos; + if (_project.usesSwiftPackageManager) { + await _swiftPackageManager.generatePluginsSwiftPackage( + _plugins, + platform, + xcodeProject, + ); + } else if (xcodeProject.flutterPluginSwiftPackageInProjectSettings) { + // If Swift Package Manager is not enabled but the project is already + // integrated for Swift Package Manager, pass no plugins to the generator. + // This will still generate the required Package.swift, but it will have + // no dependencies. + await _swiftPackageManager.generatePluginsSwiftPackage( + [], + platform, + xcodeProject, + ); + } + + // Skip updating Podfile if project is a module, since it will use a + // different module-specific Podfile. + if (_project.isModule) { + return; + } + final (:int totalCount, :int swiftPackageCount, :int podCount) = await _evaluatePluginsAndPrintWarnings( + platform: platform, + xcodeProject: xcodeProject, + ); + + final bool useCocoapods; + if (_project.usesSwiftPackageManager) { + useCocoapods = _usingCocoaPodsPlugin( + pluginCount: totalCount, + swiftPackageCount: swiftPackageCount, + cocoapodCount: podCount, + ); + } else { + // When Swift Package Manager is not enabled, set up Podfile if plugins + // is not empty, regardless of if plugins are CocoaPod compatible. This + // is done because `processPodsIfNeeded` uses `hasPlugins` to determine + // whether to run. + useCocoapods = _plugins.isNotEmpty; + } + if (useCocoapods) { + await _cocoapods.setupPodfile(xcodeProject); + } + /// The user may have a custom maintained Podfile that they're running `pod install` + /// on themselves. + else if (xcodeProject.podfile.existsSync() && xcodeProject.podfileLock.existsSync()) { + _cocoapods.addPodsDependencyToFlutterXcconfig(xcodeProject); + } + } + + bool _usingCocoaPodsPlugin({ + required int pluginCount, + required int swiftPackageCount, + required int cocoapodCount, + }) { + if (_project.usesSwiftPackageManager) { + if (pluginCount == swiftPackageCount) { + return false; + } + } + return cocoapodCount > 0; + } + + /// Returns count of total number of plugins, number of Swift Package Manager + /// compatible plugins, and number of CocoaPods compatible plugins. A plugin + /// can be both Swift Package Manager and CocoaPods compatible. + /// + /// Prints warnings when using a plugin incompatible with the available Darwin + /// Dependency Manager (Swift Package Manager or CocoaPods). + /// + /// Prints message prompting the user to deintegrate CocoaPods if using all + /// Swift Package plugins. + Future<({int totalCount, int swiftPackageCount, int podCount})> _evaluatePluginsAndPrintWarnings({ + required SupportedPlatform platform, + required XcodeBasedProject xcodeProject, + }) async { + int pluginCount = 0; + int swiftPackageCount = 0; + int cocoapodCount = 0; + for (final Plugin plugin in _plugins) { + if (plugin.platforms[platform.name] == null) { + continue; + } + final String? swiftPackagePath = plugin.pluginSwiftPackageManifestPath( + _fileSystem, + platform.name, + ); + final bool swiftPackageManagerCompatible = swiftPackagePath != null && + _fileSystem.file(swiftPackagePath).existsSync(); + + final String? podspecPath = plugin.pluginPodspecPath( + _fileSystem, + platform.name, + ); + final bool cocoaPodsCompatible = + podspecPath != null && _fileSystem.file(podspecPath).existsSync(); + + // If a plugin is missing both a Package.swift and Podspec, it won't be + // included by either Swift Package Manager or Cocoapods. This can happen + // when a plugin doesn't have native platform code. + // For example, image_picker_macos only uses dart code. + if (!swiftPackageManagerCompatible && !cocoaPodsCompatible) { + continue; + } + + pluginCount += 1; + if (swiftPackageManagerCompatible) { + swiftPackageCount += 1; + } + if (cocoaPodsCompatible) { + cocoapodCount += 1; + } + + // If not using Swift Package Manager and plugin does not have podspec + // but does have a Package.swift, throw an error. Otherwise, it'll error + // when it builds. + if (!_project.usesSwiftPackageManager && + !cocoaPodsCompatible && + swiftPackageManagerCompatible) { + throwToolExit( + 'Plugin ${plugin.name} is only Swift Package Manager compatible. Try ' + 'enabling Swift Package Manager by running ' + '"flutter config --enable-swift-package-manager" or remove the ' + 'plugin as a dependency.'); + } + } + + // Only show warnings to remove CocoaPods if the project is using Swift + // Package Manager, has already been migrated to have SPM integration, and + // all plugins are Swift Packages. + if (_project.usesSwiftPackageManager && + xcodeProject.flutterPluginSwiftPackageInProjectSettings && + pluginCount == swiftPackageCount && + swiftPackageCount != 0) { + final bool podfileExists = xcodeProject.podfile.existsSync(); + if (podfileExists) { + // If all plugins are Swift Packages and the Podfile matches the + // default template, recommend pod deintegration. + final File podfileTemplate = await _cocoapods.getPodfileTemplate( + xcodeProject, + xcodeProject.xcodeProject, + ); + + final String configWarning = '${_podIncludeInConfigWarning(xcodeProject, 'Debug')}' + '${_podIncludeInConfigWarning(xcodeProject, 'Release')}'; + + if (xcodeProject.podfile.readAsStringSync() == + podfileTemplate.readAsStringSync()) { + _logger.printWarning( + 'All plugins found for ${platform.name} are Swift Packages, but your ' + 'project still has CocoaPods integration. To remove CocoaPods ' + 'integration, complete the following steps:\n' + ' * In the ${platform.name}/ directory run "pod deintegrate"\n' + ' * Also in the ${platform.name}/ directory, delete the Podfile\n' + '$configWarning\n' + "Removing CocoaPods integration will improve the project's build time."); + } else { + // If all plugins are Swift Packages, but the Podfile has custom logic, + // recommend migrating manually. + _logger.printWarning( + 'All plugins found for ${platform.name} are Swift Packages, but your ' + 'project still has CocoaPods integration. Your project uses a ' + 'non-standard Podfile and will need to be migrated to Swift Package ' + 'Manager manually. Some steps you may need to complete include:\n' + ' * In the ${platform.name}/ directory run "pod deintegrate"\n' + ' * Transition any Pod dependencies to Swift Package equivalents. ' + 'See https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app\n' + ' * Transition any custom logic\n' + '$configWarning\n' + "Removing CocoaPods integration will improve the project's build time."); + } + } + } + + return ( + totalCount: pluginCount, + swiftPackageCount: swiftPackageCount, + podCount: cocoapodCount, + ); + } + + String _podIncludeInConfigWarning(XcodeBasedProject xcodeProject, String mode) { + final File xcconfigFile = xcodeProject.xcodeConfigFor(mode); + final bool configIncludesPods = _cocoapods.xcconfigIncludesPods(xcconfigFile); + if (configIncludesPods) { + return ' * Remove the include to ' + '"${_cocoapods.includePodsXcconfig(mode)}" in your ' + '${xcconfigFile.parent.parent.basename}/${xcconfigFile.parent.basename}/${xcconfigFile.basename}\n'; + } + + return ''; + } +} diff --git a/packages/flutter_tools/lib/src/macos/migrations/nsapplicationmain_deprecation_migration.dart b/packages/flutter_tools/lib/src/macos/migrations/nsapplicationmain_deprecation_migration.dart new file mode 100644 index 000000000000..3bca342eb57a --- /dev/null +++ b/packages/flutter_tools/lib/src/macos/migrations/nsapplicationmain_deprecation_migration.dart @@ -0,0 +1,51 @@ +// 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 '../../base/file_system.dart'; +import '../../base/project_migrator.dart'; +import '../../xcode_project.dart'; + +const String _appDelegateFileBefore = r''' +@NSApplicationMain +class AppDelegate'''; + +const String _appDelegateFileAfter = r''' +@main +class AppDelegate'''; + +/// Replace the deprecated `@NSApplicationMain` attribute with `@main`. +/// +/// See: +/// https://github.com/apple/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md +class NSApplicationMainDeprecationMigration extends ProjectMigrator { + NSApplicationMainDeprecationMigration( + MacOSProject project, + super.logger, + ) : _appDelegateSwift = project.appDelegateSwift; + + final File _appDelegateSwift; + + @override + Future migrate() async { + // Skip this migration if the project uses Objective-C. + if (!_appDelegateSwift.existsSync()) { + logger.printTrace( + 'macos/Runner/AppDelegate.swift not found, skipping @main migration.', + ); + return; + } + + // Migrate the macos/Runner/AppDelegate.swift file. + final String original = _appDelegateSwift.readAsStringSync(); + final String migrated = original.replaceFirst(_appDelegateFileBefore, _appDelegateFileAfter); + if (original == migrated) { + return; + } + + logger.printWarning( + 'macos/Runner/AppDelegate.swift uses the deprecated @NSApplicationMain attribute, updating.', + ); + _appDelegateSwift.writeAsStringSync(migrated); + } +} diff --git a/packages/flutter_tools/lib/src/macos/swift_package_manager.dart b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart new file mode 100644 index 000000000000..98ba51b592b7 --- /dev/null +++ b/packages/flutter_tools/lib/src/macos/swift_package_manager.dart @@ -0,0 +1,196 @@ +// 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 '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/template.dart'; +import '../base/version.dart'; +import '../plugins.dart'; +import '../project.dart'; +import 'swift_packages.dart'; + +/// Swift Package Manager is a dependency management solution for iOS and macOS +/// applications. +/// +/// See also: +/// * https://www.swift.org/documentation/package-manager/ - documentation on +/// Swift Package Manager. +/// * https://developer.apple.com/documentation/packagedescription/package - +/// documentation on Swift Package Manager manifest file, Package.swift. +class SwiftPackageManager { + const SwiftPackageManager({ + required FileSystem fileSystem, + required TemplateRenderer templateRenderer, + }) : _fileSystem = fileSystem, + _templateRenderer = templateRenderer; + + final FileSystem _fileSystem; + final TemplateRenderer _templateRenderer; + + static const String _defaultFlutterPluginsSwiftPackageName = 'FlutterGeneratedPluginSwiftPackage'; + + static final SwiftPackageSupportedPlatform iosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform( + platform: SwiftPackagePlatform.ios, + version: Version(12, 0, null), + ); + + static final SwiftPackageSupportedPlatform macosSwiftPackageSupportedPlatform = SwiftPackageSupportedPlatform( + platform: SwiftPackagePlatform.macos, + version: Version(10, 14, null), + ); + + /// Creates a Swift Package called 'FlutterGeneratedPluginSwiftPackage' that + /// has dependencies on Flutter plugins that are compatible with Swift + /// Package Manager. + Future generatePluginsSwiftPackage( + List plugins, + SupportedPlatform platform, + XcodeBasedProject project, + ) async { + _validatePlatform(platform); + + final ( + List packageDependencies, + List targetDependencies + ) = _dependenciesForPlugins(plugins, platform); + + // If there aren't any Swift Package plugins and the project hasn't been + // migrated yet, don't generate a Swift package or migrate the app since + // it's not needed. If the project has already been migrated, regenerate + // the Package.swift even if there are no dependencies in case there + // were dependencies previously. + if (packageDependencies.isEmpty && !project.flutterPluginSwiftPackageInProjectSettings) { + return; + } + + final SwiftPackageSupportedPlatform swiftSupportedPlatform; + if (platform == SupportedPlatform.ios) { + swiftSupportedPlatform = iosSwiftPackageSupportedPlatform; + } else { + swiftSupportedPlatform = macosSwiftPackageSupportedPlatform; + } + + // FlutterGeneratedPluginSwiftPackage must be statically linked to ensure + // any dynamic dependencies are linked to Runner and prevent undefined symbols. + final SwiftPackageProduct generatedProduct = SwiftPackageProduct( + name: _defaultFlutterPluginsSwiftPackageName, + targets: [_defaultFlutterPluginsSwiftPackageName], + libraryType: SwiftPackageLibraryType.static, + ); + + final SwiftPackageTarget generatedTarget = SwiftPackageTarget.defaultTarget( + name: _defaultFlutterPluginsSwiftPackageName, + dependencies: targetDependencies, + ); + + final SwiftPackage pluginsPackage = SwiftPackage( + manifest: project.flutterPluginSwiftPackageManifest, + name: _defaultFlutterPluginsSwiftPackageName, + platforms: [swiftSupportedPlatform], + products: [generatedProduct], + dependencies: packageDependencies, + targets: [generatedTarget], + templateRenderer: _templateRenderer, + ); + pluginsPackage.createSwiftPackage(); + } + + (List, List) _dependenciesForPlugins( + List plugins, + SupportedPlatform platform, + ) { + final List packageDependencies = + []; + final List targetDependencies = + []; + + for (final Plugin plugin in plugins) { + final String? pluginSwiftPackageManifestPath = plugin.pluginSwiftPackageManifestPath( + _fileSystem, + platform.name, + ); + if (plugin.platforms[platform.name] == null || + pluginSwiftPackageManifestPath == null || + !_fileSystem.file(pluginSwiftPackageManifestPath).existsSync()) { + continue; + } + + packageDependencies.add(SwiftPackagePackageDependency( + name: plugin.name, + path: _fileSystem.file(pluginSwiftPackageManifestPath).parent.path, + )); + + // The target dependency product name is hyphen separated because it's + // the dependency's library name, which Swift Package Manager will + // automatically use as the CFBundleIdentifier if linked dynamically. The + // CFBundleIdentifier cannot contain underscores. + targetDependencies.add(SwiftPackageTargetDependency.product( + name: plugin.name.replaceAll('_', '-'), + packageName: plugin.name, + )); + } + return (packageDependencies, targetDependencies); + } + + /// Validates the platform is either iOS or macOS, otherwise throw an error. + static void _validatePlatform(SupportedPlatform platform) { + if (platform != SupportedPlatform.ios && + platform != SupportedPlatform.macos) { + throwToolExit( + 'The platform ${platform.name} is not compatible with Swift Package Manager. ' + 'Only iOS and macOS are allowed.', + ); + } + } + + /// If the project's IPHONEOS_DEPLOYMENT_TARGET/MACOSX_DEPLOYMENT_TARGET is + /// higher than the FlutterGeneratedPluginSwiftPackage's default + /// SupportedPlatform, increase the SupportedPlatform to match the project's + /// deployment target. + /// + /// This is done for the use case of a plugin requiring a higher iOS/macOS + /// version than FlutterGeneratedPluginSwiftPackage. + /// + /// Swift Package Manager emits an error if a dependency isn’t compatible + /// with the top-level package’s deployment version. The deployment target of + /// a package’s dependencies must be lower than or equal to the top-level + /// package’s deployment target version for a particular platform. + /// + /// To still be able to use the plugin, the user can increase the Xcode + /// project's iOS/macOS deployment target and this will then increase the + /// deployment target for FlutterGeneratedPluginSwiftPackage. + static void updateMinimumDeployment({ + required XcodeBasedProject project, + required SupportedPlatform platform, + required String deploymentTarget, + }) { + final Version? projectDeploymentTargetVersion = Version.parse(deploymentTarget); + final SwiftPackageSupportedPlatform defaultPlatform; + final SwiftPackagePlatform packagePlatform; + if (platform == SupportedPlatform.ios) { + defaultPlatform = iosSwiftPackageSupportedPlatform; + packagePlatform = SwiftPackagePlatform.ios; + } else { + defaultPlatform = macosSwiftPackageSupportedPlatform; + packagePlatform = SwiftPackagePlatform.macos; + } + + if (projectDeploymentTargetVersion == null || + projectDeploymentTargetVersion <= defaultPlatform.version || + !project.flutterPluginSwiftPackageManifest.existsSync()) { + return; + } + + final String manifestContents = project.flutterPluginSwiftPackageManifest.readAsStringSync(); + final String oldSupportedPlatform = defaultPlatform.format(); + final String newSupportedPlatform = SwiftPackageSupportedPlatform( + platform: packagePlatform, + version: projectDeploymentTargetVersion, + ).format(); + + project.flutterPluginSwiftPackageManifest.writeAsStringSync( + manifestContents.replaceFirst(oldSupportedPlatform, newSupportedPlatform), + ); + } +} diff --git a/packages/flutter_tools/lib/src/macos/swift_packages.dart b/packages/flutter_tools/lib/src/macos/swift_packages.dart new file mode 100644 index 000000000000..501fc9ebf930 --- /dev/null +++ b/packages/flutter_tools/lib/src/macos/swift_packages.dart @@ -0,0 +1,403 @@ +// 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 '../base/file_system.dart'; +import '../base/template.dart'; +import '../base/version.dart'; + +/// Swift toolchain version included with Xcode 15.0. +const String minimumSwiftToolchainVersion = '5.9'; + +const String _swiftPackageTemplate = ''' +// swift-tools-version: {{swiftToolsVersion}} +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "{{packageName}}", + {{#platforms}} + platforms: [ + {{platforms}} + ], + {{/platforms}} + products: [ + {{products}} + ], + dependencies: [ + {{dependencies}} + ], + targets: [ + {{targets}} + ] +) +'''; + +const String _swiftPackageSourceTemplate = ''' +// +// Generated file. Do not edit. +// +'''; + +const String _singleIndent = ' '; +const String _doubleIndent = '$_singleIndent$_singleIndent'; + +/// A Swift Package is reusable code that can be shared across projects and +/// with other developers in iOS and macOS applications. A Swift Package +/// requires a Package.swift. This class handles the formatting and creation of +/// a Package.swift. +/// +/// See https://developer.apple.com/documentation/packagedescription/package +/// for more information about Swift Packages and Package.swift. +class SwiftPackage { + SwiftPackage({ + required File manifest, + required String name, + required List platforms, + required List products, + required List dependencies, + required List targets, + required TemplateRenderer templateRenderer, + }) : _manifest = manifest, + _name = name, + _platforms = platforms, + _products = products, + _dependencies = dependencies, + _targets = targets, + _templateRenderer = templateRenderer; + + /// [File] for Package.swift. + final File _manifest; + + /// The name of the Swift package. + final String _name; + + /// The list of minimum versions for platforms supported by the package. + final List _platforms; + + /// The list of products that this package vends and that clients can use. + final List _products; + + /// The list of package dependencies. + final List _dependencies; + + /// The list of targets that are part of this package. + final List _targets; + + final TemplateRenderer _templateRenderer; + + /// Context for the [_swiftPackageTemplate] template. + Map get _templateContext { + return { + 'swiftToolsVersion': minimumSwiftToolchainVersion, + 'packageName': _name, + // Supported platforms can't be empty, so only include if not null. + 'platforms': _formatPlatforms() ?? false, + 'products': _formatProducts(), + 'dependencies': _formatDependencies(), + 'targets': _formatTargets(), + }; + } + + /// Create a Package.swift using settings from [_templateContext]. + void createSwiftPackage() { + // Swift Packages require at least one source file per non-binary target, + // whether it be in Swift or Objective C. If the target does not have any + // files yet, create an empty Swift file. + for (final SwiftPackageTarget target in _targets) { + if (target.targetType == SwiftPackageTargetType.binaryTarget) { + continue; + } + final Directory targetDirectory = _manifest.parent + .childDirectory('Sources') + .childDirectory(target.name); + if (!targetDirectory.existsSync() || targetDirectory.listSync().isEmpty) { + final File requiredSwiftFile = targetDirectory.childFile( + '${target.name}.swift', + ); + requiredSwiftFile.createSync(recursive: true); + requiredSwiftFile.writeAsStringSync(_swiftPackageSourceTemplate); + } + } + + final String renderedTemplate = _templateRenderer.renderString( + _swiftPackageTemplate, + _templateContext, + ); + _manifest.createSync(recursive: true); + _manifest.writeAsStringSync(renderedTemplate); + } + + String? _formatPlatforms() { + if (_platforms.isEmpty) { + return null; + } + final List platformStrings = _platforms + .map((SwiftPackageSupportedPlatform platform) => platform.format()) + .toList(); + return platformStrings.join(',\n$_doubleIndent'); + } + + String _formatProducts() { + if (_products.isEmpty) { + return ''; + } + final List libraries = _products + .map((SwiftPackageProduct product) => product.format()) + .toList(); + return libraries.join(',\n$_doubleIndent'); + } + + String _formatDependencies() { + if (_dependencies.isEmpty) { + return ''; + } + final List packages = _dependencies + .map((SwiftPackagePackageDependency dependency) => dependency.format()) + .toList(); + return packages.join(',\n$_doubleIndent'); + } + + String _formatTargets() { + if (_targets.isEmpty) { + return ''; + } + final List targetList = + _targets.map((SwiftPackageTarget target) => target.format()).toList(); + return targetList.join(',\n$_doubleIndent'); + } +} + +enum SwiftPackagePlatform { + ios(name: '.iOS'), + macos(name: '.macOS'), + tvos(name: '.tvOS'), + watchos(name: '.watchOS'); + + const SwiftPackagePlatform({required this.name}); + + final String name; +} + +/// A platform that the Swift package supports. +/// +/// Representation of SupportedPlatform from +/// https://developer.apple.com/documentation/packagedescription/supportedplatform. +class SwiftPackageSupportedPlatform { + SwiftPackageSupportedPlatform({ + required this.platform, + required this.version, + }); + + final SwiftPackagePlatform platform; + final Version version; + + String format() { + // platforms: [ + // .macOS("10.14"), + // .iOS("12.0"), + // ], + return '${platform.name}("$version")'; + } +} + +/// Types of library linking. +/// +/// Representation of Product.Library.LibraryType from +/// https://developer.apple.com/documentation/packagedescription/product/library/librarytype. +enum SwiftPackageLibraryType { + dynamic(name: '.dynamic'), + static(name: '.static'); + + const SwiftPackageLibraryType({required this.name}); + + final String name; +} + +/// An externally visible build artifact that's available to clients of the +/// package. +/// +/// Representation of Product from +/// https://developer.apple.com/documentation/packagedescription/product. +class SwiftPackageProduct { + SwiftPackageProduct({ + required this.name, + required this.targets, + this.libraryType, + }); + + final String name; + final SwiftPackageLibraryType? libraryType; + final List targets; + + String format() { + // products: [ + // .library(name: "FlutterGeneratedPluginSwiftPackage", targets: ["FlutterGeneratedPluginSwiftPackage"]), + // .library(name: "FlutterDependenciesPackage", type: .dynamic, targets: ["FlutterDependenciesPackage"]), + // ], + String targetsString = ''; + if (targets.isNotEmpty) { + final List quotedTargets = + targets.map((String target) => '"$target"').toList(); + targetsString = ', targets: [${quotedTargets.join(', ')}]'; + } + String libraryTypeString = ''; + if (libraryType != null) { + libraryTypeString = ', type: ${libraryType!.name}'; + } + return '.library(name: "$name"$libraryTypeString$targetsString)'; + } +} + +/// A package dependency of a Swift package. +/// +/// Representation of Package.Dependency from +/// https://developer.apple.com/documentation/packagedescription/package/dependency. +class SwiftPackagePackageDependency { + SwiftPackagePackageDependency({ + required this.name, + required this.path, + }); + + final String name; + final String path; + + String format() { + // dependencies: [ + // .package(name: "image_picker_ios", path: "/path/to/packages/image_picker/image_picker_ios/ios/image_picker_ios"), + // ], + return '.package(name: "$name", path: "$path")'; + } +} + +/// Type of Target constructor. +/// +/// See https://developer.apple.com/documentation/packagedescription/target for +/// more information. +enum SwiftPackageTargetType { + target(name: '.target'), + binaryTarget(name: '.binaryTarget'); + + const SwiftPackageTargetType({required this.name}); + + final String name; +} + +/// A building block of a Swift Package that contains a set of source files +/// that Swift Package Manager compiles into a module. +/// +/// Representation of Target from +/// https://developer.apple.com/documentation/packagedescription/target. +class SwiftPackageTarget { + SwiftPackageTarget.defaultTarget({ + required this.name, + this.dependencies, + }) : path = null, + targetType = SwiftPackageTargetType.target; + + SwiftPackageTarget.binaryTarget({ + required this.name, + required String relativePath, + }) : path = relativePath, + dependencies = null, + targetType = SwiftPackageTargetType.binaryTarget; + + final String name; + final String? path; + final List? dependencies; + final SwiftPackageTargetType targetType; + + String format() { + // targets: [ + // .binaryTarget( + // name: "Flutter", + // path: "Flutter.xcframework" + // ), + // .target( + // name: "FlutterGeneratedPluginSwiftPackage", + // dependencies: [ + // .target(name: "Flutter"), + // .product(name: "image_picker_ios", package: "image_picker_ios") + // ] + // ), + // ] + const String targetIndent = _doubleIndent; + const String targetDetailsIndent = '$_doubleIndent$_singleIndent'; + + final List targetDetails = []; + + final String nameString = 'name: "$name"'; + targetDetails.add(nameString); + + if (path != null) { + final String pathString = 'path: "$path"'; + targetDetails.add(pathString); + } + + if (dependencies != null && dependencies!.isNotEmpty) { + final List targetDependencies = dependencies! + .map((SwiftPackageTargetDependency dependency) => dependency.format()) + .toList(); + final String dependenciesString = ''' +dependencies: [ +${targetDependencies.join(",\n")} +$targetDetailsIndent]'''; + targetDetails.add(dependenciesString); + } + + return ''' +${targetType.name}( +$targetDetailsIndent${targetDetails.join(",\n$targetDetailsIndent")} +$targetIndent)'''; + } +} + +/// Type of Target.Dependency constructor. +/// +/// See https://developer.apple.com/documentation/packagedescription/target/dependency +/// for more information. +enum SwiftPackageTargetDependencyType { + product(name: '.product'), + target(name: '.target'); + + const SwiftPackageTargetDependencyType({required this.name}); + + final String name; +} + +/// A dependency for the Target on a product from a package dependency or from +/// another Target in the same package. +/// +/// Representation of Target.Dependency from +/// https://developer.apple.com/documentation/packagedescription/target/dependency. +class SwiftPackageTargetDependency { + SwiftPackageTargetDependency.product({ + required this.name, + required String packageName, + }) : package = packageName, + dependencyType = SwiftPackageTargetDependencyType.product; + + SwiftPackageTargetDependency.target({ + required this.name, + }) : package = null, + dependencyType = SwiftPackageTargetDependencyType.target; + + final String name; + final String? package; + final SwiftPackageTargetDependencyType dependencyType; + + String format() { + // dependencies: [ + // .target(name: "Flutter"), + // .product(name: "image_picker_ios", package: "image_picker_ios") + // ] + if (dependencyType == SwiftPackageTargetDependencyType.product) { + return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name", package: "$package")'; + } + return '$_doubleIndent$_doubleIndent${dependencyType.name}(name: "$name")'; + } +} diff --git a/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart new file mode 100644 index 000000000000..58169d592b63 --- /dev/null +++ b/packages/flutter_tools/lib/src/migrations/swift_package_manager_integration_migration.dart @@ -0,0 +1,1048 @@ +// 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:xml/xml.dart'; + +import '../base/common.dart'; +import '../base/error_handling_io.dart'; +import '../base/file_system.dart'; +import '../base/logger.dart'; +import '../base/project_migrator.dart'; +import '../build_info.dart'; +import '../convert.dart'; +import '../ios/plist_parser.dart'; +import '../ios/xcodeproj.dart'; +import '../project.dart'; + +/// Swift Package Manager integration requires changes to the Xcode project's +/// project.pbxproj and xcscheme. This class handles making those changes. +class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { + SwiftPackageManagerIntegrationMigration( + XcodeBasedProject project, + SupportedPlatform platform, + BuildInfo buildInfo, { + required XcodeProjectInterpreter xcodeProjectInterpreter, + required Logger logger, + required FileSystem fileSystem, + required PlistParser plistParser, + }) : _xcodeProject = project, + _platform = platform, + _buildInfo = buildInfo, + _xcodeProjectInfoFile = project.xcodeProjectInfoFile, + _xcodeProjectInterpreter = xcodeProjectInterpreter, + _fileSystem = fileSystem, + _plistParser = plistParser, + super(logger); + + final XcodeBasedProject _xcodeProject; + final SupportedPlatform _platform; + final BuildInfo _buildInfo; + final XcodeProjectInterpreter _xcodeProjectInterpreter; + final FileSystem _fileSystem; + final File _xcodeProjectInfoFile; + final PlistParser _plistParser; + + /// New identifier for FlutterGeneratedPluginSwiftPackage PBXBuildFile. + static const String _flutterPluginsSwiftPackageBuildFileIdentifier = '78A318202AECB46A00862997'; + + /// New identifier for FlutterGeneratedPluginSwiftPackage XCLocalSwiftPackageReference. + static const String _localFlutterPluginsSwiftPackageReferenceIdentifer = '781AD8BC2B33823900A9FFBB'; + + /// New identifier for FlutterGeneratedPluginSwiftPackage XCSwiftPackageProductDependency. + static const String _flutterPluginsSwiftPackageProductDependencyIdentifer = '78A3181F2AECB46A00862997'; + + /// Existing iOS identifier for Runner PBXFrameworksBuildPhase. + static const String _iosRunnerFrameworksBuildPhaseIdentifer = '97C146EB1CF9000F007C117D'; + + /// Existing macOS identifier for Runner PBXFrameworksBuildPhase. + static const String _macosRunnerFrameworksBuildPhaseIdentifer = '33CC10EA2044A3C60003C045'; + + /// Existing iOS identifier for Runner PBXNativeTarget. + static const String _iosRunnerNativeTargetIdentifer = '97C146ED1CF9000F007C117D'; + + /// Existing macOS identifier for Runner PBXNativeTarget. + static const String _macosRunnerNativeTargetIdentifer = '33CC10EC2044A3C60003C045'; + + /// Existing iOS identifier for Runner PBXProject. + static const String _iosProjectIdentifier = '97C146E61CF9000F007C117D'; + + /// Existing macOS identifier for Runner PBXProject. + static const String _macosProjectIdentifier = '33CC10E52044A3C60003C045'; + + File get backupProjectSettings => _fileSystem + .directory(_xcodeProjectInfoFile.parent) + .childFile('project.pbxproj.backup'); + + String get _runnerFrameworksBuildPhaseIdentifer { + return _platform == SupportedPlatform.ios + ? _iosRunnerFrameworksBuildPhaseIdentifer + : _macosRunnerFrameworksBuildPhaseIdentifer; + } + + String get _runnerNativeTargetIdentifer { + return _platform == SupportedPlatform.ios + ? _iosRunnerNativeTargetIdentifer + : _macosRunnerNativeTargetIdentifer; + } + + String get _projectIdentifier { + return _platform == SupportedPlatform.ios + ? _iosProjectIdentifier + : _macosProjectIdentifier; + } + + void restoreFromBackup(SchemeInfo? schemeInfo) { + if (backupProjectSettings.existsSync()) { + logger.printTrace('Restoring project settings from backup file...'); + backupProjectSettings.copySync(_xcodeProject.xcodeProjectInfoFile.path); + } + schemeInfo?.backupSchemeFile?.copySync(schemeInfo.schemeFile.path); + } + + /// Add Swift Package Manager integration to Xcode project's project.pbxproj + /// and Runner.xcscheme. + /// + /// If migration fails or project.pbxproj or Runner.xcscheme becomes invalid, + /// will revert any changes made and throw an error. + @override + Future migrate() async { + Status? migrationStatus; + SchemeInfo? schemeInfo; + try { + if (!_xcodeProjectInfoFile.existsSync()) { + throw Exception('Xcode project not found.'); + } + + schemeInfo = await _getSchemeFile(); + + // Check for specific strings in the xcscheme and pbxproj to see if the + // project has been already migrated, whether automatically or manually. + final bool isSchemeMigrated = _isSchemeMigrated(schemeInfo); + final bool isPbxprojMigrated = _xcodeProject.flutterPluginSwiftPackageInProjectSettings; + if (isSchemeMigrated && isPbxprojMigrated) { + return; + } + + migrationStatus = logger.startProgress( + 'Adding Swift Package Manager integration...', + ); + + if (isSchemeMigrated) { + logger.printTrace('${schemeInfo.schemeFile.basename} already migrated. Skipping...'); + } else { + _migrateScheme(schemeInfo); + } + if (isPbxprojMigrated) { + logger.printTrace('${_xcodeProjectInfoFile.basename} already migrated. Skipping...'); + } else { + _migratePbxproj(); + } + + logger.printTrace('Validating project settings...'); + + // Re-parse the project settings to check for syntax errors. + final ParsedProjectInfo updatedInfo = _parsePbxproj(); + + // If pbxproj was not already migrated, verify settings were set correctly. + if (!isPbxprojMigrated) { + if (!_isPbxprojMigratedCorrectly(updatedInfo, logErrorIfNotMigrated: true)) { + throw Exception('Settings were not updated correctly.'); + } + } + + // Get the project info to make sure it compiles with xcodebuild + await _xcodeProjectInterpreter.getInfo( + _xcodeProject.hostAppRoot.path, + ); + } on Exception catch (e) { + restoreFromBackup(schemeInfo); + // TODO(vashworth): Add link to instructions on how to manually integrate + // once available on website. + throwToolExit( + 'An error occurred when adding Swift Package Manager integration:\n' + ' $e\n\n' + 'Swift Package Manager is currently an experimental feature, please file a bug at\n' + ' https://github.com/flutter/flutter/issues/new?template=1_activation.yml \n' + 'Consider including a copy of the following files in your bug report:\n' + ' ${_platform.name}/Runner.xcodeproj/project.pbxproj\n' + ' ${_platform.name}/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ' + '(or the scheme for the flavor used)\n\n' + 'To avoid this failure, disable Flutter Swift Package Manager integration for the project\n' + 'by adding the following in the project\'s pubspec.yaml under the "flutter" section:\n' + ' "disable-swift-package-manager: true"\n' + 'Alternatively, disable Flutter Swift Package Manager integration globally with the\n' + 'following command:\n' + ' "flutter config --no-enable-swift-package-manager"\n'); + } finally { + ErrorHandlingFileSystem.deleteIfExists(backupProjectSettings); + if (schemeInfo?.backupSchemeFile != null) { + ErrorHandlingFileSystem.deleteIfExists(schemeInfo!.backupSchemeFile!); + } + migrationStatus?.stop(); + } + } + + Future _getSchemeFile() async { + final XcodeProjectInfo? projectInfo = await _xcodeProject.projectInfo(); + if (projectInfo == null) { + throw Exception('Unable to get Xcode project info.'); + } + if (_xcodeProject.xcodeWorkspace == null) { + throw Exception('Xcode workspace not found.'); + } + final String? scheme = projectInfo.schemeFor(_buildInfo); + if (scheme == null) { + projectInfo.reportFlavorNotFoundAndExit(); + } + + final File schemeFile = _xcodeProject.xcodeProjectSchemeFile(scheme: scheme); + if (!schemeFile.existsSync()) { + throw Exception('Unable to get scheme file for $scheme.'); + } + + final String schemeContent = schemeFile.readAsStringSync(); + return SchemeInfo( + schemeName: scheme, + schemeFile: schemeFile, + schemeContent: schemeContent, + ); + } + + bool _isSchemeMigrated(SchemeInfo schemeInfo) { + if (schemeInfo.schemeContent.contains('Run Prepare Flutter Framework Script')) { + return true; + } + return false; + } + + void _migrateScheme(SchemeInfo schemeInfo) { + final File schemeFile = schemeInfo.schemeFile; + final String schemeContent = schemeInfo.schemeContent; + + // The scheme should have a BuildableReference already in it with a + // BlueprintIdentifier matching the Runner Native Target. Copy from it + // since BuildableName, BlueprintName, ReferencedContainer may have been + // changed from "Runner". Ensures the expected attributes are found. + // Example: + // + // + final List schemeLines = LineSplitter.split(schemeContent).toList(); + final int index = schemeLines.indexWhere((String line) => + line.contains('BlueprintIdentifier = "$_runnerNativeTargetIdentifer"'), + ); + if (index == -1 || index + 3 >= schemeLines.length) { + throw Exception( + 'Failed to parse ${schemeFile.basename}: Could not find BuildableReference ' + 'for ${_xcodeProject.hostAppProjectName}.'); + } + + final String buildableName = schemeLines[index + 1].trim(); + if (!buildableName.contains('BuildableName')) { + throw Exception('Failed to parse ${schemeFile.basename}: Could not find BuildableName.'); + } + + final String blueprintName = schemeLines[index + 2].trim(); + if (!blueprintName.contains('BlueprintName')) { + throw Exception('Failed to parse ${schemeFile.basename}: Could not find BlueprintName.'); + } + + final String referencedContainer = schemeLines[index + 3].trim(); + if (!referencedContainer.contains('ReferencedContainer')) { + throw Exception('Failed to parse ${schemeFile.basename}: Could not find ReferencedContainer.'); + } + + schemeInfo.backupSchemeFile = schemeFile.parent.childFile('${schemeFile.basename}.backup'); + schemeFile.copySync(schemeInfo.backupSchemeFile!.path); + + final String scriptText; + if (_platform == SupportedPlatform.ios) { + scriptText = r'scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">'; + } else { + scriptText = r'scriptText = ""$FLUTTER_ROOT"/packages/flutter_tools/bin/macos_assemble.sh prepare ">'; + } + + String newContent = ''' + + + + + + '''; + String newScheme = schemeContent; + if (schemeContent.contains('PreActions')) { + newScheme = schemeContent.replaceFirst('', '\n$newContent'); + } else { + newContent = ''' + +$newContent + +'''; + final String? buildActionEntries = schemeLines.where((String line) => line.contains('')).firstOrNull; + if (buildActionEntries == null) { + throw Exception('Failed to parse ${schemeFile.basename}: Could not find BuildActionEntries.'); + } else { + newScheme = schemeContent.replaceFirst(buildActionEntries, '$newContent$buildActionEntries'); + } + } + + schemeFile.writeAsStringSync(newScheme); + try { + XmlDocument.parse(newScheme); + } on XmlException catch (exception) { + throw Exception('Failed to parse ${schemeFile.basename}: Invalid xml: $newScheme\n$exception'); + } + } + + /// Parses the project.pbxproj into [ParsedProjectInfo]. Will throw an + /// exception if it fails to parse. + ParsedProjectInfo _parsePbxproj() { + final String? results = _plistParser.plistJsonContent( + _xcodeProjectInfoFile.path, + ); + if (results == null) { + throw Exception('Failed to parse project settings.'); + } + + try { + final Object decodeResult = json.decode(results) as Object; + if (decodeResult is! Map) { + throw Exception( + 'project.pbxproj returned unexpected JSON response: $results', + ); + } + return ParsedProjectInfo.fromJson(decodeResult); + } on FormatException { + throw Exception('project.pbxproj returned non-JSON response: $results'); + } + } + + /// Checks if all sections have been migrated. If [logErrorIfNotMigrated] is + /// true, will log an error for each section that is not migrated. + bool _isPbxprojMigratedCorrectly( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool buildFilesMigrated = _isBuildFilesMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + final bool frameworksBuildPhaseMigrated = _isFrameworksBuildPhaseMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + final bool nativeTargetsMigrated = _isNativeTargetMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + final bool projectObjectMigrated = _isProjectObjectMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + final bool localSwiftPackageMigrated = _isLocalSwiftPackageProductDependencyMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + final bool swiftPackageMigrated = _isSwiftPackageProductDependencyMigrated( + projectInfo, + logErrorIfNotMigrated: logErrorIfNotMigrated, + ); + return buildFilesMigrated && + frameworksBuildPhaseMigrated && + nativeTargetsMigrated && + projectObjectMigrated && + localSwiftPackageMigrated && + swiftPackageMigrated; + } + + void _migratePbxproj() { + final String originalProjectContents = + _xcodeProjectInfoFile.readAsStringSync(); + + _ensureNewIdentifiersNotUsed(originalProjectContents); + + // Parse project.pbxproj into JSON + final ParsedProjectInfo parsedInfo = _parsePbxproj(); + + List lines = LineSplitter.split(originalProjectContents).toList(); + lines = _migrateBuildFile(lines, parsedInfo); + lines = _migrateFrameworksBuildPhase(lines, parsedInfo); + lines = _migrateNativeTarget(lines, parsedInfo); + lines = _migrateProjectObject(lines, parsedInfo); + lines = _migrateLocalPackageProductDependencies(lines, parsedInfo); + lines = _migratePackageProductDependencies(lines, parsedInfo); + + final String newProjectContents = '${lines.join('\n')}\n'; + + if (originalProjectContents != newProjectContents) { + logger.printTrace('Updating project settings...'); + _xcodeProjectInfoFile.copySync(backupProjectSettings.path); + _xcodeProjectInfoFile.writeAsStringSync(newProjectContents); + } + } + + void _ensureNewIdentifiersNotUsed(String originalProjectContents) { + if (originalProjectContents.contains(_flutterPluginsSwiftPackageBuildFileIdentifier)) { + throw Exception('Duplicate id found for PBXBuildFile.'); + } + if (originalProjectContents.contains(_flutterPluginsSwiftPackageProductDependencyIdentifer)) { + throw Exception('Duplicate id found for XCSwiftPackageProductDependency.'); + } + if (originalProjectContents.contains(_localFlutterPluginsSwiftPackageReferenceIdentifer)) { + throw Exception('Duplicate id found for XCLocalSwiftPackageReference.'); + } + } + + bool _isBuildFilesMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.buildFileIdentifiers + .contains(_flutterPluginsSwiftPackageBuildFileIdentifier); + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXBuildFile was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateBuildFile( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isBuildFilesMigrated(projectInfo)) { + logger.printTrace('PBXBuildFile already migrated. Skipping...'); + return lines; + } + + const String newContent = + ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = $_flutterPluginsSwiftPackageProductDependencyIdentifer /* FlutterGeneratedPluginSwiftPackage */; };'; + + final (int _, int endSectionIndex) = _sectionRange('PBXBuildFile', lines); + + lines.insert(endSectionIndex, newContent); + return lines; + } + + bool _isFrameworksBuildPhaseMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.frameworksBuildPhases + .where((ParsedProjectFrameworksBuildPhase phase) => + phase.identifier == _runnerFrameworksBuildPhaseIdentifer && + phase.files != null && + phase.files!.contains(_flutterPluginsSwiftPackageBuildFileIdentifier)) + .toList() + .isNotEmpty; + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXFrameworksBuildPhase was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateFrameworksBuildPhase( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isFrameworksBuildPhaseMigrated(projectInfo)) { + logger.printTrace('PBXFrameworksBuildPhase already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange( + 'PBXFrameworksBuildPhase', + lines, + ); + + // Find index where Frameworks Build Phase for the Runner target begins. + final int runnerFrameworksPhaseStartIndex = lines.indexWhere( + (String line) => line.trim().startsWith( + '$_runnerFrameworksBuildPhaseIdentifer /* Frameworks */ = {', + ), + startSectionIndex, + ); + if (runnerFrameworksPhaseStartIndex == -1 || + runnerFrameworksPhaseStartIndex > endSectionIndex) { + throw Exception( + 'Unable to find PBXFrameworksBuildPhase for ${_xcodeProject.hostAppProjectName} target.', + ); + } + + // Get the Frameworks Build Phase for the Runner target from the parsed + // project info. + final ParsedProjectFrameworksBuildPhase? runnerFrameworksPhase = projectInfo + .frameworksBuildPhases + .where((ParsedProjectFrameworksBuildPhase phase) => + phase.identifier == _runnerFrameworksBuildPhaseIdentifer) + .toList() + .firstOrNull; + if (runnerFrameworksPhase == null) { + throw Exception( + 'Unable to find parsed PBXFrameworksBuildPhase for ${_xcodeProject.hostAppProjectName} target.', + ); + } + + if (runnerFrameworksPhase.files == null) { + // If files is null, the files field is missing and must be added. + const String newContent = ''' + files = ( + $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + );'''; + lines.insert(runnerFrameworksPhaseStartIndex + 1, newContent); + } else { + // Find the files field within the Frameworks PBXFrameworksBuildPhase for the Runner target. + final int startFilesIndex = lines.indexWhere( + (String line) => line.trim().contains('files = ('), + runnerFrameworksPhaseStartIndex, + ); + if (startFilesIndex == -1 || startFilesIndex > endSectionIndex) { + throw Exception( + 'Unable to files for PBXFrameworksBuildPhase ${_xcodeProject.hostAppProjectName} target.', + ); + } + const String newContent = + ' $_flutterPluginsSwiftPackageBuildFileIdentifier /* FlutterGeneratedPluginSwiftPackage in Frameworks */,'; + lines.insert(startFilesIndex + 1, newContent); + } + + return lines; + } + + bool _isNativeTargetMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.nativeTargets + .where((ParsedNativeTarget target) => + target.identifier == _runnerNativeTargetIdentifer && + target.packageProductDependencies != null && + target.packageProductDependencies! + .contains(_flutterPluginsSwiftPackageProductDependencyIdentifer)) + .toList() + .isNotEmpty; + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXNativeTarget was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateNativeTarget( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isNativeTargetMigrated(projectInfo)) { + logger.printTrace('PBXNativeTarget already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange('PBXNativeTarget', lines); + + // Find index where Native Target for the Runner target begins. + final ParsedNativeTarget? runnerNativeTarget = projectInfo.nativeTargets + .where((ParsedNativeTarget target) => + target.identifier == _runnerNativeTargetIdentifer) + .firstOrNull; + if (runnerNativeTarget == null) { + throw Exception( + 'Unable to find parsed PBXNativeTarget for ${_xcodeProject.hostAppProjectName} target.', + ); + } + final String subsectionLineStart = runnerNativeTarget.name != null + ? '$_runnerNativeTargetIdentifer /* ${runnerNativeTarget.name} */ = {' + : _runnerNativeTargetIdentifer; + final int runnerNativeTargetStartIndex = lines.indexWhere( + (String line) => line.trim().startsWith(subsectionLineStart), + startSectionIndex, + ); + if (runnerNativeTargetStartIndex == -1 || + runnerNativeTargetStartIndex > endSectionIndex) { + throw Exception( + 'Unable to find PBXNativeTarget for ${_xcodeProject.hostAppProjectName} target.', + ); + } + + if (runnerNativeTarget.packageProductDependencies == null) { + // If packageProductDependencies is null, the packageProductDependencies field is missing and must be added. + const List newContent = [ + ' packageProductDependencies = (', + ' $_flutterPluginsSwiftPackageProductDependencyIdentifer /* FlutterGeneratedPluginSwiftPackage */,', + ' );', + ]; + lines.insertAll(runnerNativeTargetStartIndex + 1, newContent); + } else { + // Find the packageProductDependencies field within the Native Target for the Runner target. + final int packageProductDependenciesIndex = lines.indexWhere( + (String line) => line.trim().contains('packageProductDependencies'), + runnerNativeTargetStartIndex, + ); + if (packageProductDependenciesIndex == -1 || packageProductDependenciesIndex > endSectionIndex) { + throw Exception( + 'Unable to find packageProductDependencies for ${_xcodeProject.hostAppProjectName} PBXNativeTarget.', + ); + } + const String newContent = + ' $_flutterPluginsSwiftPackageProductDependencyIdentifer /* FlutterGeneratedPluginSwiftPackage */,'; + lines.insert(packageProductDependenciesIndex + 1, newContent); + } + return lines; + } + + bool _isProjectObjectMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.projects + .where((ParsedProject target) => + target.identifier == _projectIdentifier && + target.packageReferences != null && + target.packageReferences! + .contains(_localFlutterPluginsSwiftPackageReferenceIdentifer)) + .toList() + .isNotEmpty; + if (logErrorIfNotMigrated && !migrated) { + logger.printError('PBXProject was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateProjectObject( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isProjectObjectMigrated(projectInfo)) { + logger.printTrace('PBXProject already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange('PBXProject', lines); + + // Find index where Runner Project begins. + final int projectStartIndex = lines.indexWhere( + (String line) => line + .trim() + .startsWith('$_projectIdentifier /* Project object */ = {'), + startSectionIndex, + ); + if (projectStartIndex == -1 || projectStartIndex > endSectionIndex) { + throw Exception( + 'Unable to find PBXProject for ${_xcodeProject.hostAppProjectName}.', + ); + } + + // Get the Runner project from the parsed project info. + final ParsedProject? projectObject = projectInfo.projects + .where( + (ParsedProject project) => project.identifier == _projectIdentifier) + .toList() + .firstOrNull; + if (projectObject == null) { + throw Exception( + 'Unable to find parsed PBXProject for ${_xcodeProject.hostAppProjectName}.', + ); + } + + if (projectObject.packageReferences == null) { + // If packageReferences is null, the packageReferences field is missing and must be added. + const List newContent = [ + ' packageReferences = (', + ' $_localFlutterPluginsSwiftPackageReferenceIdentifer /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,', + ' );', + ]; + lines.insertAll(projectStartIndex + 1, newContent); + } else { + // Find the packageReferences field within the Runner project. + final int packageReferencesIndex = lines.indexWhere( + (String line) => line.trim().contains('packageReferences'), + projectStartIndex, + ); + if (packageReferencesIndex == -1 || packageReferencesIndex > endSectionIndex) { + throw Exception( + 'Unable to find packageReferences for ${_xcodeProject.hostAppProjectName} PBXProject.', + ); + } + const String newContent = + ' $_localFlutterPluginsSwiftPackageReferenceIdentifer /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,'; + lines.insert(packageReferencesIndex + 1, newContent); + } + return lines; + } + + bool _isLocalSwiftPackageProductDependencyMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.localSwiftPackageProductDependencies + .contains(_localFlutterPluginsSwiftPackageReferenceIdentifer); + if (logErrorIfNotMigrated && !migrated) { + logger.printError('XCLocalSwiftPackageReference was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migrateLocalPackageProductDependencies( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isLocalSwiftPackageProductDependencyMigrated(projectInfo)) { + logger.printTrace('XCLocalSwiftPackageReference already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange( + 'XCLocalSwiftPackageReference', + lines, + throwIfMissing: false, + ); + + if (startSectionIndex == -1) { + // There isn't a XCLocalSwiftPackageReference section yet, so add it + final List newContent = [ + '/* Begin XCLocalSwiftPackageReference section */', + ' $_localFlutterPluginsSwiftPackageReferenceIdentifer /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {', + ' isa = XCLocalSwiftPackageReference;', + ' relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;', + ' };', + '/* End XCLocalSwiftPackageReference section */', + ]; + + final int index = lines + .lastIndexWhere((String line) => line.trim().startsWith('/* End')); + if (index == -1) { + throw Exception('Unable to find any sections.'); + } + lines.insertAll(index + 1, newContent); + + return lines; + } + + final List newContent = [ + ' $_localFlutterPluginsSwiftPackageReferenceIdentifer /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {', + ' isa = XCLocalSwiftPackageReference;', + ' relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;', + ' };', + ]; + + lines.insertAll(endSectionIndex, newContent); + + return lines; + } + + bool _isSwiftPackageProductDependencyMigrated( + ParsedProjectInfo projectInfo, { + bool logErrorIfNotMigrated = false, + }) { + final bool migrated = projectInfo.swiftPackageProductDependencies + .contains(_flutterPluginsSwiftPackageProductDependencyIdentifer); + if (logErrorIfNotMigrated && !migrated) { + logger.printError('XCSwiftPackageProductDependency was not migrated or was migrated incorrectly.'); + } + return migrated; + } + + List _migratePackageProductDependencies( + List lines, + ParsedProjectInfo projectInfo, + ) { + if (_isSwiftPackageProductDependencyMigrated(projectInfo)) { + logger.printTrace('XCSwiftPackageProductDependency already migrated. Skipping...'); + return lines; + } + + final (int startSectionIndex, int endSectionIndex) = _sectionRange( + 'XCSwiftPackageProductDependency', + lines, + throwIfMissing: false, + ); + + if (startSectionIndex == -1) { + // There isn't a XCSwiftPackageProductDependency section yet, so add it + final List newContent = [ + '/* Begin XCSwiftPackageProductDependency section */', + ' $_flutterPluginsSwiftPackageProductDependencyIdentifer /* FlutterGeneratedPluginSwiftPackage */ = {', + ' isa = XCSwiftPackageProductDependency;', + ' productName = FlutterGeneratedPluginSwiftPackage;', + ' };', + '/* End XCSwiftPackageProductDependency section */', + ]; + + final int index = lines + .lastIndexWhere((String line) => line.trim().startsWith('/* End')); + if (index == -1) { + throw Exception('Unable to find any sections.'); + } + lines.insertAll(index + 1, newContent); + + return lines; + } + + final List newContent = [ + ' $_flutterPluginsSwiftPackageProductDependencyIdentifer /* FlutterGeneratedPluginSwiftPackage */ = {', + ' isa = XCSwiftPackageProductDependency;', + ' productName = FlutterGeneratedPluginSwiftPackage;', + ' };', + ]; + + lines.insertAll(endSectionIndex, newContent); + + return lines; + } + + (int, int) _sectionRange( + String sectionName, + List lines, { + bool throwIfMissing = true, + }) { + final int startSectionIndex = + lines.indexOf('/* Begin $sectionName section */'); + if (throwIfMissing && startSectionIndex == -1) { + throw Exception('Unable to find beginning of $sectionName section.'); + } + final int endSectionIndex = lines.indexOf('/* End $sectionName section */'); + if (throwIfMissing && endSectionIndex == -1) { + throw Exception('Unable to find end of $sectionName section.'); + } + if (throwIfMissing && startSectionIndex > endSectionIndex) { + throw Exception( + 'Found the end of $sectionName section before the beginning.', + ); + } + return (startSectionIndex, endSectionIndex); + } +} + +class SchemeInfo { + SchemeInfo({ + required this.schemeName, + required this.schemeFile, + required this.schemeContent, + }); + + final String schemeName; + final File schemeFile; + final String schemeContent; + File? backupSchemeFile; +} + +/// Representation of data parsed from Xcode project's project.pbxproj. +class ParsedProjectInfo { + ParsedProjectInfo._({ + required this.buildFileIdentifiers, + required this.fileReferenceIdentifiers, + required this.parsedGroups, + required this.frameworksBuildPhases, + required this.nativeTargets, + required this.projects, + required this.swiftPackageProductDependencies, + required this.localSwiftPackageProductDependencies, + }); + + factory ParsedProjectInfo.fromJson(Map data) { + final List buildFiles = []; + final List references = []; + final List groups = []; + final List buildPhases = + []; + final List native = []; + final List project = []; + final List parsedSwiftPackageProductDependencies = []; + final List parsedLocalSwiftPackageProductDependencies = []; + + if (data['objects'] is Map) { + final Map values = + data['objects']! as Map; + for (final String key in values.keys) { + if (values[key] is Map) { + final Map details = + values[key]! as Map; + if (details['isa'] is String) { + final String objectType = details['isa']! as String; + if (objectType == 'PBXBuildFile') { + buildFiles.add(key); + } else if (objectType == 'PBXFileReference') { + references.add(key); + } else if (objectType == 'PBXGroup') { + groups.add(ParsedProjectGroup.fromJson(key, details)); + } else if (objectType == 'PBXFrameworksBuildPhase') { + buildPhases.add( + ParsedProjectFrameworksBuildPhase.fromJson(key, details)); + } else if (objectType == 'PBXNativeTarget') { + native.add(ParsedNativeTarget.fromJson(key, details)); + } else if (objectType == 'PBXProject') { + project.add(ParsedProject.fromJson(key, details)); + } else if (objectType == 'XCSwiftPackageProductDependency') { + parsedSwiftPackageProductDependencies.add(key); + } else if (objectType == 'XCLocalSwiftPackageReference') { + parsedLocalSwiftPackageProductDependencies.add(key); + } + } + } + } + } + + return ParsedProjectInfo._( + buildFileIdentifiers: buildFiles, + fileReferenceIdentifiers: references, + parsedGroups: groups, + frameworksBuildPhases: buildPhases, + nativeTargets: native, + projects: project, + swiftPackageProductDependencies: parsedSwiftPackageProductDependencies, + localSwiftPackageProductDependencies: + parsedLocalSwiftPackageProductDependencies, + ); + } + + /// List of identifiers under PBXBuildFile section. + List buildFileIdentifiers; + + /// List of identifiers under PBXFileReference section. + List fileReferenceIdentifiers; + + /// List of [ParsedProjectGroup] items under PBXGroup section. + List parsedGroups; + + /// List of [ParsedProjectFrameworksBuildPhase] items under PBXFrameworksBuildPhase section. + List frameworksBuildPhases; + + /// List of [ParsedNativeTarget] items under PBXNativeTarget section. + List nativeTargets; + + /// List of [ParsedProject] items under PBXProject section. + List projects; + + /// List of identifiers under XCSwiftPackageProductDependency section. + List swiftPackageProductDependencies; + + /// List of identifiers under XCLocalSwiftPackageReference section. + /// Introduced in Xcode 15. + List localSwiftPackageProductDependencies; +} + +/// Representation of data parsed from PBXGroup section in Xcode project's project.pbxproj. +class ParsedProjectGroup { + ParsedProjectGroup._(this.identifier, this.children, this.name); + + factory ParsedProjectGroup.fromJson(String key, Map data) { + String? name; + if (data['name'] is String) { + name = data['name']! as String; + } else if (data['path'] is String) { + name = data['path']! as String; + } + + final List parsedChildren = []; + if (data['children'] is List) { + for (final Object? item in data['children']! as List) { + if (item is String) { + parsedChildren.add(item); + } + } + return ParsedProjectGroup._(key, parsedChildren, name); + } + return ParsedProjectGroup._(key, null, name); + } + + final String identifier; + final List? children; + final String? name; +} + +/// Representation of data parsed from PBXFrameworksBuildPhase section in Xcode +/// project's project.pbxproj. +class ParsedProjectFrameworksBuildPhase { + ParsedProjectFrameworksBuildPhase._(this.identifier, this.files); + + factory ParsedProjectFrameworksBuildPhase.fromJson( + String key, Map data) { + final List parsedFiles = []; + if (data['files'] is List) { + for (final Object? item in data['files']! as List) { + if (item is String) { + parsedFiles.add(item); + } + } + return ParsedProjectFrameworksBuildPhase._(key, parsedFiles); + } + return ParsedProjectFrameworksBuildPhase._(key, null); + } + + final String identifier; + final List? files; +} + +/// Representation of data parsed from PBXNativeTarget section in Xcode project's +/// project.pbxproj. +class ParsedNativeTarget { + ParsedNativeTarget._( + this.data, + this.identifier, + this.name, + this.packageProductDependencies, + ); + + factory ParsedNativeTarget.fromJson(String key, Map data) { + String? name; + if (data['name'] is String) { + name = data['name']! as String; + } + + final List parsedChildren = []; + if (data['packageProductDependencies'] is List) { + for (final Object? item + in data['packageProductDependencies']! as List) { + if (item is String) { + parsedChildren.add(item); + } + } + return ParsedNativeTarget._(data, key, name, parsedChildren); + } + return ParsedNativeTarget._(data, key, name, null); + } + + final Map data; + final String identifier; + final String? name; + final List? packageProductDependencies; +} + +/// Representation of data parsed from PBXProject section in Xcode project's +/// project.pbxproj. +class ParsedProject { + ParsedProject._( + this.data, + this.identifier, + this.packageReferences, + ); + + factory ParsedProject.fromJson(String key, Map data) { + final List parsedChildren = []; + if (data['packageReferences'] is List) { + for (final Object? item in data['packageReferences']! as List) { + if (item is String) { + parsedChildren.add(item); + } + } + return ParsedProject._(data, key, parsedChildren); + } + return ParsedProject._(data, key, null); + } + + final Map data; + final String identifier; + final List? packageReferences; +} diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index f43309a4e7c8..d8e1c80f5c6d 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -397,6 +397,51 @@ class Plugin { /// Whether this plugin is a direct dependency of the app. /// If [false], the plugin is a dependency of another plugin. final bool isDirectDependency; + + /// Expected path to the plugin's Package.swift. Returns null if the plugin + /// does not support the [platform] or the [platform] is not iOS or macOS. + String? pluginSwiftPackageManifestPath( + FileSystem fileSystem, + String platform, + ) { + final String? platformDirectoryName = _darwinPluginDirectoryName(platform); + if (platformDirectoryName == null) { + return null; + } + return fileSystem.path.join( + path, + platformDirectoryName, + name, + 'Package.swift', + ); + } + + /// Expected path to the plugin's podspec. Returns null if the plugin does + /// not support the [platform] or the [platform] is not iOS or macOS. + String? pluginPodspecPath(FileSystem fileSystem, String platform) { + final String? platformDirectoryName = _darwinPluginDirectoryName(platform); + if (platformDirectoryName == null) { + return null; + } + return fileSystem.path.join(path, platformDirectoryName, '$name.podspec'); + } + + String? _darwinPluginDirectoryName(String platform) { + final PluginPlatform? platformPlugin = platforms[platform]; + if (platformPlugin == null || + (platform != IOSPlugin.kConfigKey && + platform != MacOSPlugin.kConfigKey)) { + return null; + } + + // iOS and macOS code can be shared in "darwin" directory, otherwise + // respectively in "ios" or "macos" directories. + if (platformPlugin is DarwinPlugin && + (platformPlugin as DarwinPlugin).sharedDarwinSource) { + return 'darwin'; + } + return platform; + } } /// Metadata associated with the resolution of a platform interface of a plugin. diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index b4c2e6939908..0d8f849583cb 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -21,6 +21,7 @@ import 'features.dart'; import 'flutter_manifest.dart'; import 'flutter_plugins.dart'; import 'globals.dart' as globals; +import 'macos/xcode.dart'; import 'platform_plugins.dart'; import 'project_validator_result.dart'; import 'template.dart'; @@ -31,14 +32,20 @@ export 'xcode_project.dart'; /// Enum for each officially supported platform. enum SupportedPlatform { - android, - ios, - linux, - macos, - web, - windows, - fuchsia, - root, // Special platform to represent the root project directory + android(name: 'android'), + ios(name: 'ios'), + linux(name: 'linux'), + macos(name: 'macos'), + web(name: 'web'), + windows(name: 'windows'), + fuchsia(name: 'fuchsia'), + root(name: 'root'); // Special platform to represent the root project directory + + const SupportedPlatform({ + required this.name, + }); + + final String name; } class FlutterProjectFactory { @@ -261,6 +268,24 @@ class FlutterProject { /// True if this project has an example application. bool get hasExampleApp => _exampleDirectory(directory).existsSync(); + /// True if this project doesn't have Swift Package Manager disabled in the + /// pubspec, has either an iOS or macOS platform implementation, is not a + /// module project, Xcode is 15 or greater, and the Swift Package Manager + /// feature is enabled. + bool get usesSwiftPackageManager { + if (!manifest.disabledSwiftPackageManager && + (ios.existsSync() || macos.existsSync()) && + !isModule) { + final Xcode? xcode = globals.xcode; + final Version? xcodeVersion = xcode?.currentVersion; + if (xcodeVersion == null || xcodeVersion.major < 15) { + return false; + } + return featureFlags.isSwiftPackageManagerEnabled; + } + return false; + } + /// Returns a list of platform names that are supported by the project. List getSupportedPlatforms({bool includeRoot = false}) { final List platforms = includeRoot ? [SupportedPlatform.root] : []; diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index addb06a6ff12..1eea4d9c1795 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -391,6 +391,7 @@ class FlutterDevice { logger: globals.logger, processManager: globals.processManager, artifacts: globals.artifacts!, + buildMode: buildInfo.mode, ); return devFS!.create(); } diff --git a/packages/flutter_tools/lib/src/update_packages_pins.dart b/packages/flutter_tools/lib/src/update_packages_pins.dart index 640c792ba6a6..bea1cceab017 100644 --- a/packages/flutter_tools/lib/src/update_packages_pins.dart +++ b/packages/flutter_tools/lib/src/update_packages_pins.dart @@ -2,10 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// This constant is in its own library so that the test exemption bot knows +// The constant below is in its own library so that the test exemption bot knows // that changing a pin does not require a new test. These pins are already // tested as part of the analysis shard. +// Pub packages are rolled automatically by the flutter-pub-roller-bot. +// For the latest status, see: +// https://github.com/pulls?q=author%3Aflutter-pub-roller-bot + /// Map from package name to package version, used to artificially pin a pub /// package version in cases when upgrading to the latest breaks Flutter. /// @@ -26,5 +30,4 @@ const Map kManuallyPinnedDependencies = { 'path_provider_android': '2.2.1', // https://github.com/flutter/flutter/issues/140796 'camera_android': '0.10.8+17', // https://github.com/flutter/flutter/issues/146004 - 'frontend_server_client': '3.2.0', // https://github.com/flutter/flutter/issues/146164 }; diff --git a/packages/flutter_tools/lib/src/web/compiler_config.dart b/packages/flutter_tools/lib/src/web/compiler_config.dart index 352baa5b0e3b..7f868bb1d6bc 100644 --- a/packages/flutter_tools/lib/src/web/compiler_config.dart +++ b/packages/flutter_tools/lib/src/web/compiler_config.dart @@ -38,6 +38,7 @@ sealed class WebCompilerConfig { Map get _buildKeyMap => { 'optimizationLevel': optimizationLevel, + 'webRenderer': renderer.name, }; } diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index 16defd560f47..e4ca075d8320 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -115,6 +115,46 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { .childDirectory('Pods') .childDirectory('Target Support Files') .childDirectory('Pods-Runner'); + + /// The directory in the project that is managed by Flutter. As much as + /// possible, files that are edited by Flutter tooling after initial project + /// creation should live here. + Directory get managedDirectory => hostAppRoot.childDirectory('Flutter'); + + /// The subdirectory of [managedDirectory] that contains files that are + /// generated on the fly. All generated files that are not intended to be + /// checked in should live here. + Directory get ephemeralDirectory => managedDirectory + .childDirectory('ephemeral'); + + /// The Flutter generated directory for the Swift Package handling plugin + /// dependencies. + Directory get flutterPluginSwiftPackageDirectory => ephemeralDirectory + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage'); + + /// The Flutter generated Swift Package manifest (Package.swift) for plugin + /// dependencies. + File get flutterPluginSwiftPackageManifest => + flutterPluginSwiftPackageDirectory.childFile('Package.swift'); + + /// Checks if FlutterGeneratedPluginSwiftPackage has been added to the + /// project's build settings by checking the contents of the pbxproj. + bool get flutterPluginSwiftPackageInProjectSettings { + return xcodeProjectInfoFile.existsSync() && + xcodeProjectInfoFile + .readAsStringSync() + .contains('FlutterGeneratedPluginSwiftPackage'); + } + + Future projectInfo() async { + final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter; + if (!xcodeProject.existsSync() || xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) { + return null; + } + return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path); + } + XcodeProjectInfo? _projectInfo; } /// Represents the iOS sub-project of a Flutter project. @@ -167,26 +207,26 @@ class IosProject extends XcodeBasedProject { /// Whether the Flutter application has an iOS project. bool get exists => hostAppRoot.existsSync(); - /// Put generated files here. - Directory get ephemeralDirectory => _flutterLibRoot.childDirectory('Flutter').childDirectory('ephemeral'); + @override + Directory get managedDirectory => _flutterLibRoot.childDirectory('Flutter'); @override - File xcodeConfigFor(String mode) => _flutterLibRoot.childDirectory('Flutter').childFile('$mode.xcconfig'); + File xcodeConfigFor(String mode) => managedDirectory.childFile('$mode.xcconfig'); @override - File get generatedEnvironmentVariableExportScript => _flutterLibRoot.childDirectory('Flutter').childFile('flutter_export_environment.sh'); + File get generatedEnvironmentVariableExportScript => managedDirectory.childFile('flutter_export_environment.sh'); + + File get appFrameworkInfoPlist => managedDirectory.childFile('AppFrameworkInfo.plist'); - File get appFrameworkInfoPlist => _flutterLibRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist'); + /// The 'AppDelegate.swift' file of the host app. This file might not exist if the app project uses Objective-C. + File get appDelegateSwift => _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift'); File get infoPlist => _editableDirectory.childDirectory('Runner').childFile('Info.plist'); Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks'); - /// True, if the app project is using swift. - bool get isSwift { - final File appDelegateSwift = _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift'); - return appDelegateSwift.existsSync(); - } + /// True if the app project uses Swift. + bool get isSwift => appDelegateSwift.existsSync(); /// Do all plugins support arm64 simulators to run natively on an ARM Mac? Future pluginsSupportArmSimulator() async { @@ -444,15 +484,6 @@ class IosProject extends XcodeBasedProject { final Map> _buildSettingsByBuildContext = >{}; - Future projectInfo() async { - final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter; - if (!xcodeProject.existsSync() || xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) { - return null; - } - return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path); - } - XcodeProjectInfo? _projectInfo; - Future?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async { final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter; if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) { @@ -692,16 +723,6 @@ class MacOSProject extends XcodeBasedProject { @override Directory get hostAppRoot => parent.directory.childDirectory('macos'); - /// The directory in the project that is managed by Flutter. As much as - /// possible, files that are edited by Flutter tooling after initial project - /// creation should live here. - Directory get managedDirectory => hostAppRoot.childDirectory('Flutter'); - - /// The subdirectory of [managedDirectory] that contains files that are - /// generated on the fly. All generated files that are not intended to be - /// checked in should live here. - Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral'); - /// The xcfilelist used to track the inputs for the Flutter script phase in /// the Xcode build. File get inputFileList => ephemeralDirectory.childFile('FlutterInputs.xcfilelist'); @@ -715,6 +736,9 @@ class MacOSProject extends XcodeBasedProject { File get pluginRegistrantImplementation => managedDirectory.childFile('GeneratedPluginRegistrant.swift'); + /// The 'AppDelegate.swift' file of the host app. This file might not exist if the app project uses Objective-C. + File get appDelegateSwift => hostAppRoot.childDirectory('Runner').childFile('AppDelegate.swift'); + @override File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig'); diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index c4e8470ca687..0f874ca705c3 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: html: 0.15.4 http: 0.13.6 intl: 0.19.0 - meta: 1.12.0 + meta: 1.14.0 multicast_dns: 0.3.2+6 mustache_template: 2.0.0 package_config: 2.1.0 @@ -36,7 +36,7 @@ dependencies: webkit_inspection_protocol: 1.2.1 xml: 6.5.0 yaml: 3.1.2 - native_stack_traces: 0.5.6 + native_stack_traces: 0.5.7 shelf: 1.4.1 vm_snapshot_analysis: 0.7.6 uuid: 3.0.7 @@ -52,7 +52,7 @@ dependencies: http_multi_server: 3.2.1 convert: 3.1.1 async: 2.11.0 - unified_analytics: 5.8.8 + unified_analytics: 5.8.8+1 cli_config: 0.2.0 graphs: 2.3.1 @@ -62,13 +62,12 @@ dependencies: # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading # this, make sure the tests are still running correctly. - test_api: 0.7.0 - test_core: 0.6.0 + test_api: 0.7.1 + test_core: 0.6.2 - vm_service: 14.2.0 + vm_service: 14.2.1 standard_message_codec: 0.0.1+4 - frontend_server_client: 3.2.0 # exact pin because of https://github.com/flutter/flutter/issues/146164 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -83,6 +82,7 @@ dependencies: dtd: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" extension_discovery: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fixnum: 1.1.0 # 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_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -114,10 +114,10 @@ dev_dependencies: js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" json_annotation: 4.8.1 # 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" - test: 1.25.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test: 1.25.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: e525 +# PUBSPEC CHECKSUM: 1789 diff --git a/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index 14af272eea67..1581057b21b8 100644 --- a/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -10,6 +10,9 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C80F4294D02FB00263BE5 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 331C80F3294D02FB00263BE5 /* RunnerTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + {{#withSwiftPackageManager}} + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + {{/withSwiftPackageManager}} 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -71,6 +74,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + {{#withSwiftPackageManager}} + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + {{/withSwiftPackageManager}} ); runOnlyForDeploymentPostprocessing = 0; }; @@ -176,6 +182,11 @@ dependencies = ( ); name = Runner; + {{#withSwiftPackageManager}} + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + {{/withSwiftPackageManager}} productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -208,6 +219,11 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + {{#withSwiftPackageManager}} + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + {{/withSwiftPackageManager}} productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -619,6 +635,22 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +{{#withSwiftPackageManager}} + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ +{{/withSwiftPackageManager}} }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl similarity index 79% rename from packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl index 3d0fb007b17a..542e2ae70000 100644 --- a/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/flutter_tools/templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl @@ -5,6 +5,26 @@ + {{#withSwiftPackageManager}} + + + + + + + + + + + {{/withSwiftPackageManager}} {{#withPlatformChannelPluginHook}} +{{#withSwiftPackageManager}} +// If your plugin has been explicitly set to "type: .dynamic" in the Package.swift, +// you will need to add your plugin as a dependency of RunnerTests within Xcode. +{{/withSwiftPackageManager}} + @import {{pluginProjectName}}; // This demonstrates a simple unit test of the Objective-C portion of this plugin's implementation. diff --git a/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl index d54d3bc9b3af..8043764061d6 100644 --- a/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl +++ b/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl @@ -11,6 +11,9 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + {{#withSwiftPackageManager}} + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + {{/withSwiftPackageManager}} 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -62,6 +65,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + {{#withSwiftPackageManager}} + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + {{/withSwiftPackageManager}} ); runOnlyForDeploymentPostprocessing = 0; }; @@ -157,6 +163,11 @@ dependencies = ( ); name = Runner; + {{#withSwiftPackageManager}} + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + {{/withSwiftPackageManager}} productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -190,6 +201,11 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + {{#withSwiftPackageManager}} + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + {{/withSwiftPackageManager}} productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -620,6 +636,22 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +{{#withSwiftPackageManager}} + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ +{{/withSwiftPackageManager}} }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl similarity index 79% rename from packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl index 8e3ca5dfe193..844b54e6c065 100644 --- a/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/flutter_tools/templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl @@ -5,6 +5,26 @@ + {{#withSwiftPackageManager}} + + + + + + + + + + + {{/withSwiftPackageManager}} + {{#withSwiftPackageManager}} + + + + + + + + + + + {{/withSwiftPackageManager}} Bool { return true diff --git a/packages/flutter_tools/templates/app_shared/macos.tmpl/RunnerTests/RunnerTests.swift.tmpl b/packages/flutter_tools/templates/app_shared/macos.tmpl/RunnerTests/RunnerTests.swift.tmpl index d0177b51eb56..04a7a3d2298b 100644 --- a/packages/flutter_tools/templates/app_shared/macos.tmpl/RunnerTests/RunnerTests.swift.tmpl +++ b/packages/flutter_tools/templates/app_shared/macos.tmpl/RunnerTests/RunnerTests.swift.tmpl @@ -3,6 +3,11 @@ import FlutterMacOS import XCTest {{#withPlatformChannelPluginHook}} +{{#withSwiftPackageManager}} +// If your plugin has been explicitly set to "type: .dynamic" in the Package.swift, +// you will need to add your plugin as a dependency of RunnerTests within Xcode. +{{/withSwiftPackageManager}} + @testable import {{pluginProjectName}} // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. diff --git a/packages/flutter_tools/templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl index 375c9a211b5c..65f69259b9a9 100644 --- a/packages/flutter_tools/templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl +++ b/packages/flutter_tools/templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl @@ -13,8 +13,14 @@ Pod::Spec.new do |s| s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } + {{#withSwiftPackageManager}} + s.source_files = '{{projectName}}/Sources/{{projectName}}/**/*' + s.public_header_files = '{{projectName}}/Sources/{{projectName}}/include/{{projectName}}/**/*.h' + {{/withSwiftPackageManager}} + {{^withSwiftPackageManager}} s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' + {{/withSwiftPackageManager}} s.dependency 'Flutter' s.platform = :ios, '12.0' diff --git a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl index c47c9b0d8699..c1cde67129ae 100644 --- a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl +++ b/packages/flutter_tools/templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl @@ -13,7 +13,12 @@ Pod::Spec.new do |s| s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } + {{#withSwiftPackageManager}} + s.source_files = '{{projectName}}/Sources/{{projectName}}/**/*' + {{/withSwiftPackageManager}} + {{^withSwiftPackageManager}} s.source_files = 'Classes/**/*' + {{/withSwiftPackageManager}} s.dependency 'Flutter' s.platform = :ios, '12.0' diff --git a/packages/flutter_tools/templates/plugin/ios-objc.tmpl/Classes/pluginClass.h.tmpl b/packages/flutter_tools/templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.h.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/ios-objc.tmpl/Classes/pluginClass.h.tmpl rename to packages/flutter_tools/templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.h.tmpl diff --git a/packages/flutter_tools/templates/plugin/ios-objc.tmpl/Classes/pluginClass.m.tmpl b/packages/flutter_tools/templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.m.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/ios-objc.tmpl/Classes/pluginClass.m.tmpl rename to packages/flutter_tools/templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.m.tmpl diff --git a/packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/pluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin_cocoapods/ios-swift.tmpl/Classes/pluginClass.swift.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/ios-swift.tmpl/Classes/pluginClass.swift.tmpl rename to packages/flutter_tools/templates/plugin_cocoapods/ios-swift.tmpl/Classes/pluginClass.swift.tmpl diff --git a/packages/flutter_tools/templates/plugin/ios.tmpl/Assets/.gitkeep b/packages/flutter_tools/templates/plugin_cocoapods/ios.tmpl/Assets/.gitkeep similarity index 100% rename from packages/flutter_tools/templates/plugin/ios.tmpl/Assets/.gitkeep rename to packages/flutter_tools/templates/plugin_cocoapods/ios.tmpl/Assets/.gitkeep diff --git a/packages/flutter_tools/templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin_cocoapods/macos.tmpl/Classes/pluginClass.swift.tmpl similarity index 100% rename from packages/flutter_tools/templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl rename to packages/flutter_tools/templates/plugin_cocoapods/macos.tmpl/Classes/pluginClass.swift.tmpl diff --git a/packages/flutter_tools/templates/plugin_shared/macos.tmpl/projectName.podspec.tmpl b/packages/flutter_tools/templates/plugin_shared/macos.tmpl/projectName.podspec.tmpl index 72bae61e8a73..dc63236d2278 100644 --- a/packages/flutter_tools/templates/plugin_shared/macos.tmpl/projectName.podspec.tmpl +++ b/packages/flutter_tools/templates/plugin_shared/macos.tmpl/projectName.podspec.tmpl @@ -20,7 +20,12 @@ Pod::Spec.new do |s| # `../src/*` so that the C sources can be shared among all target platforms. {{/withFfiPluginHook}} s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + {{#withSwiftPackageManager}} + s.source_files = '{{projectName}}/Sources/{{projectName}}/**/*' + {{/withSwiftPackageManager}} + {{^withSwiftPackageManager}} + s.source_files = 'Classes/**/*' + {{/withSwiftPackageManager}} s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Package.swift.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Package.swift.tmpl new file mode 100644 index 000000000000..31b08529fc23 --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Package.swift.tmpl @@ -0,0 +1,27 @@ +// swift-tools-version: {{swiftToolsVersion}} +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "{{projectName}}", + platforms: [ + {{iosSupportedPlatform}} + ], + products: [ + .library(name: "{{swiftLibraryName}}", targets: ["{{projectName}}"]) + ], + dependencies: [], + targets: [ + .target( + name: "{{projectName}}", + dependencies: [], + resources: [ + .process("Resources"), + ], + cSettings: [ + .headerSearchPath("include/{{projectName}}"), + ] + ) + ] +) diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/include/projectName.tmpl/pluginClass.h.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/include/projectName.tmpl/pluginClass.h.tmpl new file mode 100644 index 000000000000..560ffa569c73 --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/include/projectName.tmpl/pluginClass.h.tmpl @@ -0,0 +1,4 @@ +#import + +@interface {{pluginClass}} : NSObject +@end diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.m.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.m.tmpl new file mode 100644 index 000000000000..1aa97c644ea6 --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.m.tmpl @@ -0,0 +1,20 @@ +#import "{{pluginClass}}.h" + +@implementation {{pluginClass}} ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel + methodChannelWithName:@"{{projectName}}" + binaryMessenger:[registrar messenger]]; + {{pluginClass}}* instance = [[{{pluginClass}} alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"getPlatformVersion" isEqualToString:call.method]) { + result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); + } else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Package.swift.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Package.swift.tmpl new file mode 100644 index 000000000000..b387147eb20d --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Package.swift.tmpl @@ -0,0 +1,24 @@ +// swift-tools-version: {{swiftToolsVersion}} +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "{{projectName}}", + platforms: [ + {{iosSupportedPlatform}} + ], + products: [ + .library(name: "{{swiftLibraryName}}", targets: ["{{projectName}}"]) + ], + dependencies: [], + targets: [ + .target( + name: "{{projectName}}", + dependencies: [], + resources: [ + .process("Resources"), + ] + ) + ] +) diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl new file mode 100644 index 000000000000..9b8e116b4f50 --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl @@ -0,0 +1,19 @@ +import Flutter +import UIKit + +public class {{pluginClass}}: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "{{projectName}}", binaryMessenger: registrar.messenger()) + let instance = {{pluginClass}}() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/packages/integration_test/integration_test_macos/macos/Assets/.gitkeep b/packages/flutter_tools/templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep similarity index 100% rename from packages/integration_test/integration_test_macos/macos/Assets/.gitkeep rename to packages/flutter_tools/templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Package.swift.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Package.swift.tmpl new file mode 100644 index 000000000000..8625943765ba --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Package.swift.tmpl @@ -0,0 +1,24 @@ +// swift-tools-version: {{swiftToolsVersion}} +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "{{projectName}}", + platforms: [ + {{macosSupportedPlatform}} + ], + products: [ + .library(name: "{{swiftLibraryName}}", targets: ["{{projectName}}"]) + ], + dependencies: [], + targets: [ + .target( + name: "{{projectName}}", + dependencies: [], + resources: [ + .process("Resources"), + ] + ) + ] +) diff --git a/packages/integration_test/ios/Assets/.gitkeep b/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep similarity index 100% rename from packages/integration_test/ios/Assets/.gitkeep rename to packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep diff --git a/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl b/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl new file mode 100644 index 000000000000..d782168ed289 --- /dev/null +++ b/packages/flutter_tools/templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl @@ -0,0 +1,19 @@ +import Cocoa +import FlutterMacOS + +public class {{pluginClass}}: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "{{projectName}}", binaryMessenger: registrar.messenger) + let instance = {{pluginClass}}() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index a42c182fc23b..fe5c970e5d96 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -40,13 +40,13 @@ "templates/app_shared/android.tmpl/gradle/wrapper/gradle-wrapper.properties.tmpl", "templates/app_shared/android.tmpl/settings.gradle", "templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl", - "templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme", + "templates/app_shared/ios-objc.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl", "templates/app_shared/ios-objc.tmpl/Runner/AppDelegate.h", "templates/app_shared/ios-objc.tmpl/Runner/AppDelegate.m", "templates/app_shared/ios-objc.tmpl/Runner/main.m", "templates/app_shared/ios-objc.tmpl/RunnerTests/RunnerTests.m.tmpl", "templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl", - "templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme", + "templates/app_shared/ios-swift.tmpl/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme.tmpl", "templates/app_shared/ios-swift.tmpl/Runner/AppDelegate.swift", "templates/app_shared/ios-swift.tmpl/Runner/Runner-Bridging-Header.h", "templates/app_shared/ios-swift.tmpl/RunnerTests/RunnerTests.swift.tmpl", @@ -277,13 +277,9 @@ "templates/plugin/android.tmpl/gradle.properties.tmpl", "templates/plugin/android.tmpl/settings.gradle.tmpl", "templates/plugin/android.tmpl/src/main/AndroidManifest.xml.tmpl", - "templates/plugin/ios-objc.tmpl/Classes/pluginClass.h.tmpl", - "templates/plugin/ios-objc.tmpl/Classes/pluginClass.m.tmpl", "templates/plugin/ios-objc.tmpl/projectName.podspec.tmpl", - "templates/plugin/ios-swift.tmpl/Classes/pluginClass.swift.tmpl", "templates/plugin/ios-swift.tmpl/projectName.podspec.tmpl", "templates/plugin/ios.tmpl/.gitignore", - "templates/plugin/ios.tmpl/Assets/.gitkeep", "templates/plugin/lib/projectName.dart.tmpl", "templates/plugin/lib/projectName_platform_interface.dart.tmpl", "templates/plugin/lib/projectName_method_channel.dart.tmpl", @@ -292,7 +288,6 @@ "templates/plugin/linux.tmpl/pluginClassSnakeCase.cc.tmpl", "templates/plugin/linux.tmpl/pluginClassSnakeCase_private.h.tmpl", "templates/plugin/linux.tmpl/test/pluginClassSnakeCase_test.cc.tmpl", - "templates/plugin/macos.tmpl/Classes/pluginClass.swift.tmpl", "templates/plugin/README.md.tmpl", "templates/plugin/test/projectName_test.dart.tmpl", "templates/plugin/test/projectName_method_channel_test.dart.tmpl", @@ -338,6 +333,22 @@ "templates/plugin_shared/pubspec.yaml.tmpl", "templates/plugin_shared/windows.tmpl/.gitignore", + "templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.h.tmpl", + "templates/plugin_cocoapods/ios-objc.tmpl/Classes/pluginClass.m.tmpl", + "templates/plugin_cocoapods/ios-swift.tmpl/Classes/pluginClass.swift.tmpl", + "templates/plugin_cocoapods/ios.tmpl/Assets/.gitkeep", + "templates/plugin_cocoapods/macos.tmpl/Classes/pluginClass.swift.tmpl", + + "templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/include/projectName.tmpl/pluginClass.h.tmpl", + "templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.m.tmpl", + "templates/plugin_swift_package_manager/ios-objc.tmpl/projectName.tmpl/Package.swift.tmpl", + "templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl", + "templates/plugin_swift_package_manager/ios-swift.tmpl/projectName.tmpl/Package.swift.tmpl", + "templates/plugin_swift_package_manager/ios.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep", + "templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/Resources/.gitkeep", + "templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Sources/projectName.tmpl/pluginClass.swift.tmpl", + "templates/plugin_swift_package_manager/macos.tmpl/projectName.tmpl/Package.swift.tmpl", + "templates/skeleton/assets/images/2.0x/flutter_logo.png.img.tmpl", "templates/skeleton/assets/images/3.0x/flutter_logo.png.img.tmpl", "templates/skeleton/assets/images/flutter_logo.png.img.tmpl", diff --git a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart index 7ff6ac46a733..bfe67bab6b47 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart @@ -66,9 +66,11 @@ void main() { expect(projectUnderTest.ios.deprecatedCompiledDartFramework, isNot(exists)); expect(projectUnderTest.ios.deprecatedProjectFlutterFramework, isNot(exists)); expect(projectUnderTest.ios.flutterPodspec, isNot(exists)); + expect(projectUnderTest.ios.flutterPluginSwiftPackageDirectory, isNot(exists)); expect(projectUnderTest.linux.ephemeralDirectory, isNot(exists)); expect(projectUnderTest.macos.ephemeralDirectory, isNot(exists)); + expect(projectUnderTest.macos.flutterPluginSwiftPackageDirectory, isNot(exists)); expect(projectUnderTest.windows.ephemeralDirectory, isNot(exists)); expect(projectUnderTest.flutterPluginsFile, isNot(exists)); @@ -239,9 +241,11 @@ FlutterProject setupProjectUnderTest(Directory currentDirectory, bool setupXcode projectUnderTest.ios.deprecatedCompiledDartFramework.createSync(recursive: true); projectUnderTest.ios.deprecatedProjectFlutterFramework.createSync(recursive: true); projectUnderTest.ios.flutterPodspec.createSync(recursive: true); + projectUnderTest.ios.flutterPluginSwiftPackageDirectory.createSync(recursive: true); projectUnderTest.linux.ephemeralDirectory.createSync(recursive: true); projectUnderTest.macos.ephemeralDirectory.createSync(recursive: true); + projectUnderTest.macos.flutterPluginSwiftPackageDirectory.createSync(recursive: true); projectUnderTest.windows.ephemeralDirectory.createSync(recursive: true); projectUnderTest.flutterPluginsFile.createSync(recursive: true); projectUnderTest.flutterPluginsDependenciesFile.createSync(recursive: true); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart index a3230581de18..ad80e02bacc6 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart @@ -87,6 +87,7 @@ void main() { globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin'), globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin_ffi'), globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin_shared'), + globals.fs.path.join('flutter', 'packages', 'flutter_tools', 'templates', 'plugin_cocoapods'), ]; for (final String templatePath in templatePaths) { globals.fs.directory(templatePath).createSync(recursive: true); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart index d869309eceae..ec4882562d58 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart @@ -190,7 +190,7 @@ void main() { 'for normal use as it more likely to contain serious regressions.\n' '\n' 'For information on contributing to Flutter, see our contributing guide:\n' - ' https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md\n' + ' https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md\n' '\n' 'For the most up to date stable version of flutter, consider using the "beta" channel ' 'instead. The Flutter "beta" channel enjoys all the same automated testing as the ' diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart index 78a267a1d2b2..a8975081ae1a 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'dart:io' as io; import 'package:args/command_runner.dart'; +import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart' show templateAndroidGradlePluginVersion, templateAndroidGradlePluginVersionForModule, templateDefaultGradleVersion; @@ -718,6 +719,55 @@ void main() { ); }); + testUsingContext('swift plugin project with Swift Package Manager', () async { + return _createProject( + projectDir, + ['--no-pub', '--template=plugin', '--ios-language', 'swift', '--platforms', 'ios,macos'], + [ + 'ios/flutter_project/Package.swift', + 'ios/flutter_project/Sources/flutter_project/FlutterProjectPlugin.swift', + 'ios/flutter_project/Sources/flutter_project/Resources/.gitkeep', + 'macos/flutter_project/Package.swift', + 'macos/flutter_project/Sources/flutter_project/FlutterProjectPlugin.swift', + 'macos/flutter_project/Sources/flutter_project/Resources/.gitkeep', + ], + unexpectedPaths: [ + 'ios/Classes/FlutterProjectPlugin.swift', + 'macos/Classes/FlutterProjectPlugin.swift', + 'ios/Classes/FlutterProjectPlugin.h', + 'ios/Classes/FlutterProjectPlugin.m', + 'ios/Assets/.gitkeep', + 'macos/Assets/.gitkeep', + ], + ); + }, overrides: { + FeatureFlags: () => TestFeatureFlags( + isSwiftPackageManagerEnabled: true, + isMacOSEnabled: true, + ), + }); + + testUsingContext('objc plugin project with Swift Package Manager', () async { + return _createProject( + projectDir, + ['--no-pub', '--template=plugin', '--ios-language', 'objc', '--platforms', 'ios'], + [ + 'ios/flutter_project/Package.swift', + 'ios/flutter_project/Sources/flutter_project/include/flutter_project/FlutterProjectPlugin.h', + 'ios/flutter_project/Sources/flutter_project/FlutterProjectPlugin.m', + 'ios/flutter_project/Sources/flutter_project/Resources/.gitkeep', + ], + unexpectedPaths: [ + 'ios/Classes/FlutterProjectPlugin.swift', + 'ios/Classes/FlutterProjectPlugin.h', + 'ios/Classes/FlutterProjectPlugin.m', + 'ios/Assets/.gitkeep', + ], + ); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + }); + testUsingContext('plugin project with custom org', () async { return _createProject( projectDir, @@ -3700,6 +3750,40 @@ void main() { expect(rawManifestJson.contains(expectedDescription), isTrue); }); + + testUsingContext('flutter create should tool exit if the template manifest cannot be read', () async { + globals.fs.file(globals.fs.path.join( + Cache.flutterRoot!, + 'packages', + 'flutter_tools', + 'templates', + 'template_manifest.json', + )).createSync(recursive: true); + + final CreateCommand command = CreateCommand(); + final CommandRunner runner = createTestCommandRunner(command); + + await expectLater( + runner.run([ + 'create', + '--no-pub', + '--template=plugin', + '--project-name=test', + projectDir.path, + ]), + throwsToolExit(message: 'Unable to read the template manifest at path'), + ); + }, overrides: { + FileSystem: () => MemoryFileSystem.test( + opHandle: (String context, FileSystemOp operation) { + if (operation == FileSystemOp.read && context.contains('template_manifest.json')) { + throw io.PathNotFoundException( + context, const OSError(), 'Cannot open file'); + } + }, + ), + ProcessManager: () => fakeProcessManager, + }); } Future _createProject( diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart index d0a015697b35..8a2953ee8739 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -610,7 +610,8 @@ flutter: fileSystem: globals.fs, artifacts: globals.artifacts!, logger: testLogger, - projectDir: globals.fs.currentDirectory + projectDir: globals.fs.currentDirectory, + buildMode: BuildMode.debug, ); expect(testLogger.warningText, contains('Expected Error Text')); @@ -737,6 +738,7 @@ flutter: artifacts: globals.artifacts!, logger: testLogger, projectDir: globals.fs.currentDirectory, + buildMode: BuildMode.debug, ); }, overrides: { @@ -789,6 +791,7 @@ flutter: artifacts: globals.artifacts!, logger: testLogger, projectDir: globals.fs.currentDirectory, + buildMode: BuildMode.debug, ); }, overrides: { @@ -876,6 +879,7 @@ flutter: artifacts: globals.artifacts!, logger: testLogger, projectDir: globals.fs.currentDirectory, + buildMode: BuildMode.debug, ); expect((globals.processManager as FakeProcessManager).hasRemainingExpectations, false); }, overrides: { diff --git a/packages/flutter_tools/test/general.shard/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart index 20b85f4f0eb2..f0509e7d0272 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -49,12 +49,6 @@ const List kDefaultClang = [ ]; void main() { - group('SnapshotType', () { - test('does not throw, if target platform is null', () { - expect(() => SnapshotType(null, BuildMode.release), returnsNormally); - }); - }); - group('GenSnapshot', () { late GenSnapshot genSnapshot; late Artifacts artifacts; diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart index 02aea1e3f062..dc84653cf99c 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/asset_transformer_test.dart @@ -8,6 +8,7 @@ import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/tools/asset_transformer.dart'; import 'package:flutter_tools/src/flutter_manifest.dart'; @@ -53,6 +54,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, dartBinaryPath: artifacts.getArtifactPath(Artifact.engineDartBinary), + buildMode: BuildMode.debug, ); final AssetTransformationFailure? transformationFailure = await transformer.transformAsset( @@ -69,6 +71,7 @@ void main() { ], ) ], + logger: logger, ); expect(transformationFailure, isNull, reason: logger.errorText); @@ -112,6 +115,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, dartBinaryPath: dartBinaryPath, + buildMode: BuildMode.debug, ); final AssetTransformationFailure? failure = await transformer.transformAsset( @@ -124,6 +128,7 @@ void main() { args: [], ) ], + logger: BufferLogger.test(), ); expect(asset, exists); @@ -171,6 +176,7 @@ Something went wrong'''); processManager: processManager, fileSystem: fileSystem, dartBinaryPath: dartBinaryPath, + buildMode: BuildMode.debug, ); final AssetTransformationFailure? failure = await transformer.transformAsset( @@ -183,6 +189,7 @@ Something went wrong'''); args: [], ) ], + logger: BufferLogger.test(), ); expect(processManager, hasNoRemainingExpectations); @@ -265,6 +272,7 @@ Transformation failed, but I forgot to exit with a non-zero code.''' processManager: processManager, fileSystem: fileSystem, dartBinaryPath: dartBinaryPath, + buildMode: BuildMode.debug, ); final AssetTransformationFailure? failure = await transformer.transformAsset( @@ -281,6 +289,7 @@ Transformation failed, but I forgot to exit with a non-zero code.''' args: [], ), ], + logger: BufferLogger.test(), ); expect(processManager, hasNoRemainingExpectations); @@ -289,7 +298,7 @@ Transformation failed, but I forgot to exit with a non-zero code.''' expect(fileSystem.directory('.tmp_rand0').listSync(), isEmpty, reason: 'Transformer did not clean up after itself.'); }); - testWithoutContext('prints an error when a transformer in a chain (thats not the first) does not produce an output', () async { + testWithoutContext("prints an error when a transformer in a chain (that's not the first) does not produce an output", () async { final FileSystem fileSystem = MemoryFileSystem(); final Artifacts artifacts = Artifacts.test(); @@ -331,7 +340,10 @@ Transformation failed, but I forgot to exit with a non-zero code.''' onRun: (List args) { // Do nothing. }, - stderr: 'Transformation failed, but I forgot to exit with a non-zero code.' + stderr: 'Transformation failed, but I forgot to exit with a non-zero code.', + environment: const { + 'FLUTTER_BUILD_MODE': 'debug', + }, ), ]); @@ -339,6 +351,7 @@ Transformation failed, but I forgot to exit with a non-zero code.''' processManager: processManager, fileSystem: fileSystem, dartBinaryPath: dartBinaryPath, + buildMode: BuildMode.debug, ); final AssetTransformationFailure? failure = await transformer.transformAsset( @@ -355,6 +368,7 @@ Transformation failed, but I forgot to exit with a non-zero code.''' args: [], ), ], + logger: BufferLogger.test(), ); expect(failure, isNotNull); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart index cd9fc31799c6..6d33eeec2972 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/assets_test.dart @@ -38,7 +38,9 @@ void main() { fileSystem: fileSystem, logger: BufferLogger.test(), platform: FakePlatform(), - defines: {}, + defines: { + kBuildMode: BuildMode.debug.cliName, + }, ); fileSystem.file(environment.buildDir.childFile('app.dill')).createSync(recursive: true); fileSystem.file('packages/flutter_tools/lib/src/build_system/targets/assets.dart') @@ -178,7 +180,9 @@ flutter: fileSystem: fileSystem, logger: logger, platform: globals.platform, - defines: {}, + defines: { + kBuildMode: BuildMode.debug.cliName, + }, ); await fileSystem.file('.packages').create(); @@ -262,7 +266,9 @@ flutter: fileSystem: fileSystem, logger: logger, platform: globals.platform, - defines: {}, + defines: { + kBuildMode: BuildMode.debug.cliName, + }, ); await fileSystem.file('.packages').create(); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart index 0eab969458e9..79a778e06758 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart @@ -64,10 +64,10 @@ void main() { iosEnvironment.buildDir.createSync(recursive: true); }); - testWithoutContext('KernelSnapshot throws error if missing build mode', () async { + testWithoutContext('KernelSnapshotProgram throws error if missing build mode', () async { androidEnvironment.defines.remove(kBuildMode); expect( - const KernelSnapshot().build(androidEnvironment), + const KernelSnapshotProgram().build(androidEnvironment), throwsA(isA())); }); @@ -79,13 +79,22 @@ format-version: native-assets: {} '''; - testWithoutContext('KernelSnapshot handles null result from kernel compilation', () async { + const String nonEmptyNativeAssets = ''' +format-version: + - 1 + - 0 + - 0 +native-assets: + macos_arm64: + package:my_package/my_package_bindings_generated.dart: + - absolute + - my_package.framework/my_package +'''; + + testWithoutContext('KernelSnapshotProgram handles null result from kernel compilation', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -110,27 +119,22 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', - '--native-assets', - '$build/native_assets.yaml', '--verbosity=error', 'file:///lib/main.dart', ], exitCode: 1), ]); - await expectLater(() => const KernelSnapshot().build(androidEnvironment), throwsException); + await expectLater(() => const KernelSnapshotProgram().build(androidEnvironment), throwsException); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot does use track widget creation on profile builds', () async { + testWithoutContext('KernelSnapshotProgram does use track widget creation on profile builds', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -155,28 +159,23 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', - '--native-assets', - '$build/native_assets.yaml', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot().build(androidEnvironment); + await const KernelSnapshotProgram().build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot correctly handles an empty string in ExtraFrontEndOptions', () async { + testWithoutContext('KernelSnapshotProgram correctly handles an empty string in ExtraFrontEndOptions', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -201,29 +200,24 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', - '--native-assets', - '$build/native_assets.yaml', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot() + await const KernelSnapshotProgram() .build(androidEnvironment..defines[kExtraFrontEndOptions] = ''); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot correctly forwards FrontendServerStarterPath', () async { + testWithoutContext('KernelSnapshotProgram correctly forwards FrontendServerStarterPath', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -248,29 +242,24 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', - '--native-assets', - '$build/native_assets.yaml', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot() + await const KernelSnapshotProgram() .build(androidEnvironment..defines[kFrontendServerStarterPath] = 'path/to/frontend_server_starter.dart'); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot correctly forwards ExtraFrontEndOptions', () async { + testWithoutContext('KernelSnapshotProgram correctly forwards ExtraFrontEndOptions', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -295,31 +284,27 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', - '--native-assets', - '$build/native_assets.yaml', '--verbosity=error', 'foo', 'bar', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot() + await const KernelSnapshotProgram() .build(androidEnvironment..defines[kExtraFrontEndOptions] = 'foo,bar'); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot can disable track-widget-creation on debug builds', () async { + testWithoutContext('KernelSnapshotProgram can disable track-widget-creation on debug builds', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); + final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -340,33 +325,28 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', '--incremental', '--initialize-from-dill', - '$build/app.dill', - '--native-assets', - '$build/native_assets.yaml', + '$build/program.dill', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot().build(androidEnvironment + await const KernelSnapshotProgram().build(androidEnvironment ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kTrackWidgetCreation] = 'false'); expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot forces platform linking on debug for darwin target platforms', () async { + testWithoutContext('KernelSnapshotProgram forces platform linking on debug for darwin target platforms', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); - androidEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -386,20 +366,18 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', '--incremental', '--initialize-from-dill', - '$build/app.dill', - '--native-assets', - '$build/native_assets.yaml', + '$build/program.dill', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/program.dill 0\n'), ]); - await const KernelSnapshot().build(androidEnvironment + await const KernelSnapshotProgram().build(androidEnvironment ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin) ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kTrackWidgetCreation] = 'false' @@ -408,7 +386,7 @@ native-assets: {} expect(processManager, hasNoRemainingExpectations); }); - testWithoutContext('KernelSnapshot does use track widget creation on debug builds', () async { + testWithoutContext('KernelSnapshotProgram does use track widget creation on debug builds', () async { fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); @@ -423,9 +401,6 @@ native-assets: {} fileSystem: fileSystem, logger: logger, ); - testEnvironment.buildDir.childFile('native_assets.yaml') - ..createSync(recursive: true) - ..writeAsStringSync(emptyNativeAssets); final String build = testEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, @@ -447,24 +422,72 @@ native-assets: {} '--packages', '/.dart_tool/package_config.json', '--output-dill', - '$build/app.dill', + '$build/program.dill', '--depfile', '$build/kernel_snapshot.d', '--incremental', '--initialize-from-dill', - '$build/app.dill', - '--native-assets', - '$build/native_assets.yaml', + '$build/program.dill', '--verbosity=error', 'file:///lib/main.dart', - ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey /build/653e11a8e6908714056a57cd6b4f602a/app.dill 0\n'), + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey /build/653e11a8e6908714056a57cd6b4f602a/program.dill 0\n'), ]); - await const KernelSnapshot().build(testEnvironment); + await const KernelSnapshotProgram().build(testEnvironment); expect(processManager, hasNoRemainingExpectations); }); + for (final BuildMode buildMode in [BuildMode.debug, BuildMode.release]) { + for (final bool empty in [true, false]) { + final String testName = empty ? 'empty' : 'non empty'; + testWithoutContext('KernelSnapshotNativeAssets ${buildMode.name} $testName', () async { + fileSystem.file('.dart_tool/package_config.json') + ..createSync(recursive: true) + ..writeAsStringSync('{"configVersion": 2, "packages":[]}'); + androidEnvironment.buildDir.childFile('native_assets.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(empty ? emptyNativeAssets : nonEmptyNativeAssets); + final String build = androidEnvironment.buildDir.path; + final String flutterPatchedSdkPath = artifacts.getArtifactPath( + Artifact.flutterPatchedSdkPath, + platform: TargetPlatform.darwin, + mode: buildMode, + ); + processManager.addCommands([ + if (!empty) + FakeCommand(command: [ + artifacts.getArtifactPath(Artifact.engineDartAotRuntime), + '--disable-dart-dev', + artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk), + '--sdk-root', + '$flutterPatchedSdkPath/', + '--target=flutter', + '--no-print-incremental-dependencies', + ...buildModeOptions(buildMode, []), + '--no-link-platform', + if (buildMode == BuildMode.release) ...['--aot', '--tfa'], + '--packages', + '/.dart_tool/package_config.json', + '--output-dill', + '$build/native_assets.dill', + '--native-assets', + '$build/native_assets.yaml', + '--verbosity=error', + '--native-assets-only', + ], stdout: 'result $kBoundaryKey\n$kBoundaryKey\n$kBoundaryKey $build/app.dill 0\n'), + ]); + + await const KernelSnapshotNativeAssets().build(androidEnvironment + ..defines[kTargetPlatform] = getNameForTargetPlatform(TargetPlatform.darwin) + ..defines[kBuildMode] = buildMode.cliName + ); + + expect(processManager, hasNoRemainingExpectations); + }); + } + } + testUsingContext('AotElfProfile Produces correct output directory', () async { final String build = androidEnvironment.buildDir.path; processManager.addCommands([ diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart index bee12ddd04c6..73b7c25cbb8e 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_plugin_registrant_test.dart @@ -75,7 +75,7 @@ const String _kSamplePluginPubspec = ''' name: path_provider_linux description: linux implementation of the path_provider plugin // version: 2.0.1 -// homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider_linux +// homepage: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_linux flutter: plugin: diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart index cb493e7cec76..e4dc7f1ec9be 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart @@ -561,6 +561,90 @@ void main() { expect(processManager, hasNoRemainingExpectations); }); + testWithoutContext('Allow system font fallback when fontFamily is null', () async { + final Environment environment = createEnvironment({ + kIconTreeShakerFlag: 'true', + kBuildMode: 'release', + }); + final File appDill = environment.buildDir.childFile('app.dill') + ..createSync(recursive: true); + + // Valid manifest, just not using it. + fontManifestContent = DevFSStringContent(validFontManifestJson); + + final IconTreeShaker iconTreeShaker = IconTreeShaker( + environment, + fontManifestContent, + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: artifacts, + targetPlatform: TargetPlatform.android, + ); + + addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); + // Does not throw + await iconTreeShaker.subsetFont( + input: fileSystem.file(inputPath), + outputPath: outputPath, + relativePath: relativePath, + ); + + expect( + logger.traceText, + contains( + 'Expected to find fontFamily for constant IconData with codepoint: ' + '59470, but found fontFamily: null. This usually means ' + 'you are relying on the system font. Alternatively, font families in ' + 'an IconData class can be provided in the assets section of your ' + 'pubspec.yaml, or you are missing "uses-material-design: true".\n' + ), + ); + expect(processManager, hasNoRemainingExpectations); + }); + + testWithoutContext('Allow system font fallback when fontFamily is null and manifest is empty', () async { + final Environment environment = createEnvironment({ + kIconTreeShakerFlag: 'true', + kBuildMode: 'release', + }); + final File appDill = environment.buildDir.childFile('app.dill') + ..createSync(recursive: true); + + // Nothing in font manifest + fontManifestContent = DevFSStringContent(emptyFontManifestJson); + + final IconTreeShaker iconTreeShaker = IconTreeShaker( + environment, + fontManifestContent, + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: artifacts, + targetPlatform: TargetPlatform.android, + ); + + addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); + // Does not throw + await iconTreeShaker.subsetFont( + input: fileSystem.file(inputPath), + outputPath: outputPath, + relativePath: relativePath, + ); + + expect( + logger.traceText, + contains( + 'Expected to find fontFamily for constant IconData with codepoint: ' + '59470, but found fontFamily: null. This usually means ' + 'you are relying on the system font. Alternatively, font families in ' + 'an IconData class can be provided in the assets section of your ' + 'pubspec.yaml, or you are missing "uses-material-design: true".\n' + ), + ); + expect(processManager, hasNoRemainingExpectations); + }); + testWithoutContext('ConstFinder non-zero exit', () async { final Environment environment = createEnvironment({ kIconTreeShakerFlag: 'true', @@ -609,6 +693,20 @@ const String validConstFinderResult = ''' } '''; +const String emptyConstFinderResult = ''' +{ + "constantInstances": [ + { + "codePoint": 59470, + "fontFamily": null, + "fontPackage": null, + "matchTextDirection": false + } + ], + "nonConstantLocations": [] +} +'''; + const String constFinderResultWithInvalid = ''' { "constantInstances": [ @@ -668,3 +766,5 @@ const String invalidFontManifestJson = ''' ] } '''; + +const String emptyFontManifestJson = '[]'; diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index 6afa2979926e..7ef04d46d8ef 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -68,6 +68,7 @@ void main() { outputDir: globals.fs.currentDirectory.childDirectory('bar'), defines: { kTargetFile: globals.fs.path.join('foo', 'lib', 'main.dart'), + kBuildMode: BuildMode.debug.cliName, }, artifacts: Artifacts.test(), processManager: processManager, @@ -951,6 +952,68 @@ void main() { } } + test('Dart2JSTarget has unique build keys for compiler configurations', () { + const List testConfigs = [ + // Default values + JsCompilerConfig(), + + // Each individual property being made non-default + JsCompilerConfig(csp: true), + JsCompilerConfig(dumpInfo: true), + JsCompilerConfig(nativeNullAssertions: true), + JsCompilerConfig(optimizationLevel: 0), + JsCompilerConfig(noFrequencyBasedMinification: true), + JsCompilerConfig(sourceMaps: false), + JsCompilerConfig(renderer: WebRendererMode.canvaskit), + + // All properties non-default + JsCompilerConfig( + csp: true, + dumpInfo: true, + nativeNullAssertions: true, + optimizationLevel: 0, + noFrequencyBasedMinification: true, + sourceMaps: false, + renderer: WebRendererMode.canvaskit, + ), + ]; + + final Iterable buildKeys = testConfigs.map((JsCompilerConfig config) { + final Dart2JSTarget target = Dart2JSTarget(config); + return target.buildKey; + }); + + // Make sure all the build keys are unique. + expect(buildKeys.toSet().length, buildKeys.length); + }); + + test('Dart2Wasm has unique build keys for compiler configurations', () { + const List testConfigs = [ + // Default values + WasmCompilerConfig(), + + // Each individual property being made non-default + WasmCompilerConfig(optimizationLevel: 0), + WasmCompilerConfig(renderer: WebRendererMode.canvaskit), + WasmCompilerConfig(stripWasm: false), + + // All properties non-default + WasmCompilerConfig( + optimizationLevel: 0, + stripWasm: false, + renderer: WebRendererMode.canvaskit, + ), + ]; + + final Iterable buildKeys = testConfigs.map((WasmCompilerConfig config) { + final Dart2WasmTarget target = Dart2WasmTarget(config); + return target.buildKey; + }); + + // Make sure all the build keys are unique. + expect(buildKeys.toSet().length, buildKeys.length); + }); + test('Generated service worker is empty with none-strategy', () => testbed.run(() { final String fileGeneratorsPath = environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators); diff --git a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart index 687171a3e3db..a3d4c1a915e7 100644 --- a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart @@ -119,6 +119,7 @@ void main() { artifacts: artifacts, logger: BufferLogger.test(), projectDir: fileSystem.currentDirectory, + buildMode: BuildMode.debug, ); final File outputAssetFile = fileSystem.file('build/flutter_assets/my-asset.txt'); diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 2cd54da3ea72..98b9078fac42 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -135,6 +135,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); expect(() async => devFS.create(), throwsA(isA())); }); @@ -160,6 +161,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); expect(await devFS.create(), isNotNull); @@ -210,6 +212,7 @@ void main() { uploadRetryThrottle: Duration.zero, processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -245,6 +248,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -287,6 +291,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -331,6 +336,7 @@ void main() { httpClient: HttpClient(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -382,6 +388,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -461,6 +468,7 @@ void main() { }), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -507,6 +515,7 @@ void main() { httpClient: FakeHttpClient.any(), processManager: FakeProcessManager.empty(), artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -612,7 +621,8 @@ void main() { httpClient: FakeHttpClient.any(), config: Config.test(), processManager: FakeProcessManager.empty(), - artifacts: Artifacts.test(), + artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -670,7 +680,8 @@ void main() { httpClient: FakeHttpClient.any(), config: Config.test(), processManager: FakeProcessManager.empty(), - artifacts: Artifacts.test(), + artifacts: Artifacts.test(), + buildMode: BuildMode.debug, ); await devFS.create(); @@ -763,6 +774,7 @@ void main() { config: Config.test(), processManager: processManager, artifacts: artifacts, + buildMode: BuildMode.debug, ); await devFS.create(); @@ -843,6 +855,7 @@ void main() { config: Config.test(), processManager: processManager, artifacts: artifacts, + buildMode: BuildMode.debug, ); await devFS.create(); diff --git a/packages/flutter_tools/test/general.shard/emulator_test.dart b/packages/flutter_tools/test/general.shard/emulator_test.dart index 703ec37b2975..2332f066a8ce 100644 --- a/packages/flutter_tools/test/general.shard/emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/emulator_test.dart @@ -87,7 +87,7 @@ void main() { returnsNormally); }); - testUsingContext('printEmulators prints the emualtors information with header', () { + testUsingContext('printEmulators prints the emulators information with header', () { Emulator.printEmulators(emulators, testLogger); expect(testLogger.statusText, ''' diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart index c997384c6914..994ae392d6c1 100644 --- a/packages/flutter_tools/test/general.shard/features_test.dart +++ b/packages/flutter_tools/test/general.shard/features_test.dart @@ -402,5 +402,14 @@ void main() { expect(nativeAssets.stable.enabledByDefault, false); expect(nativeAssets.stable.available, false); }); + + test('${swiftPackageManager.name} availability and default enabled', () { + expect(swiftPackageManager.master.enabledByDefault, false); + expect(swiftPackageManager.master.available, true); + expect(swiftPackageManager.beta.enabledByDefault, false); + expect(swiftPackageManager.beta.available, false); + expect(swiftPackageManager.stable.enabledByDefault, false); + expect(swiftPackageManager.stable.available, false); + }); }); } diff --git a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart index a7b8ad7a98fd..c46d94503415 100644 --- a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart @@ -1415,6 +1415,39 @@ name: test expect(flutterManifest, isNotNull); expect(flutterManifest!.dependencies, isEmpty); }); + + testWithoutContext('FlutterManifest knows if Swift Package Manager is disabled', () async { + const String manifest = ''' +name: test +dependencies: + flutter: + sdk: flutter +flutter: + disable-swift-package-manager: true +'''; + final FlutterManifest flutterManifest = FlutterManifest.createFromString( + manifest, + logger: logger, + )!; + + expect(flutterManifest.disabledSwiftPackageManager, true); + }); + + testWithoutContext('FlutterManifest does not disable Swift Package Manager if missing', () async { + const String manifest = ''' +name: test +dependencies: + flutter: + sdk: flutter +flutter: +'''; + final FlutterManifest flutterManifest = FlutterManifest.createFromString( + manifest, + logger: logger, + )!; + + expect(flutterManifest.disabledSwiftPackageManager, false); + }); } Matcher matchesManifest({ diff --git a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart index 9934ca2030e8..497020031441 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart @@ -13,6 +13,7 @@ import 'package:flutter_tools/src/ios/migrations/project_base_configuration_migr import 'package:flutter_tools/src/ios/migrations/project_build_location_migration.dart'; import 'package:flutter_tools/src/ios/migrations/remove_bitcode_migration.dart'; import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embedding_migration.dart'; +import 'package:flutter_tools/src/ios/migrations/uiapplicationmain_deprecation_migration.dart'; import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/migrations/cocoapods_script_symlink.dart'; @@ -906,7 +907,7 @@ platform :ios, '12.0' project, testLogger, ); - expect(await migration.migrate(), isTrue); + await migration.migrate(); expect(xcodeProjectInfoFile.existsSync(), isFalse); expect(testLogger.traceText, contains('Xcode project not found, skipping removing bitcode migration')); @@ -922,7 +923,7 @@ platform :ios, '12.0' project, testLogger, ); - expect(await migration.migrate(), isTrue); + await migration.migrate(); expect(xcodeProjectInfoFile.lastModifiedSync(), projectLastModified); expect(xcodeProjectInfoFile.readAsStringSync(), xcodeProjectInfoFileContents); @@ -943,7 +944,7 @@ platform :ios, '12.0' project, testLogger, ); - expect(await migration.migrate(), isTrue); + await migration.migrate(); expect(xcodeProjectInfoFile.readAsStringSync(), ''' ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -1408,6 +1409,104 @@ LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frame expect(testLogger.statusText, contains('Adding input path to Thin Binary build phase.')); }); }); + + group('migrate @UIApplicationMain attribute to @main', () { + late MemoryFileSystem memoryFileSystem; + late BufferLogger testLogger; + late FakeIosProject project; + late File appDelegateFile; + + setUp(() { + memoryFileSystem = MemoryFileSystem(); + testLogger = BufferLogger.test(); + project = FakeIosProject(); + appDelegateFile = memoryFileSystem.file('AppDelegate.swift'); + project.appDelegateSwift = appDelegateFile; + }); + + testWithoutContext('skipped if files are missing', () async { + final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + expect(appDelegateFile.existsSync(), isFalse); + + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skipped if nothing to upgrade', () async { + const String appDelegateContents = ''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +'''; + appDelegateFile.writeAsStringSync(appDelegateContents); + final DateTime lastModified = appDelegateFile.lastModifiedSync(); + + final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + + expect(appDelegateFile.lastModifiedSync(), lastModified); + expect(appDelegateFile.readAsStringSync(), appDelegateContents); + + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('updates AppDelegate.swift', () async { + appDelegateFile.writeAsStringSync(''' +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +'''); + + final UIApplicationMainDeprecationMigration migration = UIApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + + expect(appDelegateFile.readAsStringSync(), ''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +'''); + expect(testLogger.warningText, contains('uses the deprecated @UIApplicationMain attribute, updating')); + }); + }); } class FakeIosProject extends Fake implements IosProject { @@ -1439,6 +1538,9 @@ class FakeIosProject extends Fake implements IosProject { @override Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner'); + + @override + File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift'); } class FakeIOSMigrator extends ProjectMigrator { diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index c95405ac6735..dc2c017a0c75 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/ios/code_signing.dart'; import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/xcresult.dart'; @@ -20,6 +21,7 @@ import 'package:test/fake.dart'; import 'package:unified_analytics/unified_analytics.dart'; import '../../src/common.dart'; +import '../../src/context.dart'; import '../../src/fake_process_manager.dart'; import '../../src/fakes.dart'; @@ -217,8 +219,16 @@ void main() { buildSettings: buildSettings, ), ); - - await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics); + final MemoryFileSystem fs = MemoryFileSystem.test(); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: FakeFlutterProject(fileSystem: fs), + ); expect(testUsage.events, contains( TestUsageEvent( 'build', @@ -310,8 +320,16 @@ Error launching application on iPhone.''', buildSettings: buildSettingsWithDevTeam, ), ); - - await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics); + final MemoryFileSystem fs = MemoryFileSystem.test(); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: FakeFlutterProject(fileSystem: fs), + ); expect( logger.errorText, contains(noProvisioningProfileInstruction), @@ -348,8 +366,16 @@ Error launching application on iPhone.''', buildSettings: buildSettingsWithDevTeam, ), ); - - await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics); + final MemoryFileSystem fs = MemoryFileSystem.test(); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: FakeFlutterProject(fileSystem: fs), + ); expect( logger.errorText, contains(missingPlatformInstructions('iOS 17.0')), @@ -388,8 +414,16 @@ Could not build the precompiled application for the device.''', buildSettings: buildSettings, ), ); - - await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics); + final MemoryFileSystem fs = MemoryFileSystem.test(); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: FakeFlutterProject(fileSystem: fs), + ); expect( logger.errorText, contains('Building a deployable iOS app requires a selected Development Team with a \nProvisioning Profile.'), @@ -432,10 +466,227 @@ Could not build the precompiled application for the device.''', ]) ); - await diagnoseXcodeBuildFailure(buildResult, testUsage, logger, fakeAnalytics); + final MemoryFileSystem fs = MemoryFileSystem.test(); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: FakeFlutterProject(fileSystem: fs), + ); expect(logger.errorText, contains('Error (Xcode): Target aot_assembly_release failed')); expect(logger.errorText, isNot(contains('Building a deployable iOS app requires a selected Development Team'))); }); + + testWithoutContext('parses redefinition of module error', () async{ + const List buildCommands = ['xcrun', 'cc', 'blah']; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stdout: '', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: buildCommands, + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + XCResultIssue.test(message: "Redefinition of module 'plugin_1_name'", subType: 'Error'), + XCResultIssue.test(message: "Redefinition of module 'plugin_2_name'", subType: 'Error'), + ]), + ); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FakeFlutterProject project = FakeFlutterProject( + fileSystem: fs, + usesSwiftPackageManager: true, + ); + project.ios.podfile.createSync(recursive: true); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: project, + ); + expect(logger.errorText, contains( + 'Your project uses both CocoaPods and Swift Package Manager, which can ' + 'cause the above error. It may be caused by there being both a CocoaPod ' + 'and Swift Package Manager dependency for the following module(s): ' + 'plugin_1_name, plugin_2_name.' + )); + }); + + testWithoutContext('parses duplicate symbols error with arch and number', () async{ + const List buildCommands = ['xcrun', 'cc', 'blah']; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stdout: r''' +duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in: + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o) + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o + duplicate symbol '_$s29plugin_1_name23PluginNamePluginCAA15UserDefaultsApiAAWP' in: + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[arm64][5](PluginNamePlugin.o) + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o +''', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: buildCommands, + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'), + ]), + ); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FakeFlutterProject project = FakeFlutterProject( + fileSystem: fs, + usesSwiftPackageManager: true, + ); + project.ios.podfile.createSync(recursive: true); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: project, + ); + expect(logger.errorText, contains( + 'Your project uses both CocoaPods and Swift Package Manager, which can ' + 'cause the above error. It may be caused by there being both a CocoaPod ' + 'and Swift Package Manager dependency for the following module(s): ' + 'plugin_1_name.' + )); + }); + + testWithoutContext('parses duplicate symbols error with number', () async{ + const List buildCommands = ['xcrun', 'cc', 'blah']; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stdout: r''' +duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in: + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name[5](PluginNamePlugin.o) + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name.o +''', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: buildCommands, + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'), + ]), + ); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FakeFlutterProject project = FakeFlutterProject( + fileSystem: fs, + usesSwiftPackageManager: true, + ); + project.ios.podfile.createSync(recursive: true); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: project, + ); + expect(logger.errorText, contains( + 'Your project uses both CocoaPods and Swift Package Manager, which can ' + 'cause the above error. It may be caused by there being both a CocoaPod ' + 'and Swift Package Manager dependency for the following module(s): ' + 'plugin_1_name.' + )); + }); + + testWithoutContext('parses duplicate symbols error without arch and number', () async{ + const List buildCommands = ['xcrun', 'cc', 'blah']; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stdout: r''' +duplicate symbol '_$s29plugin_1_name23PluginNamePluginC9setDouble3key5valueySS_SdtF' in: + /Users/username/path/to/app/build/ios/Debug-iphonesimulator/plugin_1_name/plugin_1_name.framework/plugin_1_name(PluginNamePlugin.o) +''', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: buildCommands, + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + XCResultIssue.test(message: '37 duplicate symbols', subType: 'Error'), + ]), + ); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FakeFlutterProject project = FakeFlutterProject( + fileSystem: fs, + usesSwiftPackageManager: true, + ); + project.ios.podfile.createSync(recursive: true); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: project, + ); + expect(logger.errorText, contains( + 'Your project uses both CocoaPods and Swift Package Manager, which can ' + 'cause the above error. It may be caused by there being both a CocoaPod ' + 'and Swift Package Manager dependency for the following module(s): ' + 'plugin_1_name.' + )); + }); + + testUsingContext('parses missing module error', () async{ + const List buildCommands = ['xcrun', 'cc', 'blah']; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stdout: '', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: buildCommands, + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettings, + ), + xcResult: XCResult.test(issues: [ + XCResultIssue.test(message: "Module 'plugin_1_name' not found", subType: 'Error'), + XCResultIssue.test(message: "Module 'plugin_2_name' not found", subType: 'Error'), + ]), + ); + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FakeFlutterProject project = FakeFlutterProject(fileSystem: fs); + project.ios.podfile.createSync(recursive: true); + project.directory.childFile('.packages').createSync(recursive: true); + project.manifest = FakeFlutterManifest(); + createFakePlugins(project, fs, ['plugin_1_name', 'plugin_2_name']); + fs.systemTempDirectory.childFile('cache/plugin_1_name/ios/plugin_1_name/Package.swift') + .createSync(recursive: true); + fs.systemTempDirectory.childFile('cache/plugin_2_name/ios/plugin_2_name/Package.swift') + .createSync(recursive: true); + await diagnoseXcodeBuildFailure( + buildResult, + flutterUsage: testUsage, + logger: logger, + analytics: fakeAnalytics, + fileSystem: fs, + platform: SupportedPlatform.ios, + project: project, + ); + expect(logger.errorText, contains( + 'Your project uses CocoaPods as a dependency manager, but the following plugin(s) ' + 'only support Swift Package Manager: plugin_1_name, plugin_2_name.' + )); + }); }); group('Upgrades project.pbxproj for old asset usage', () { @@ -454,9 +705,10 @@ Could not build the precompiled application for the device.''', 'another line'; testWithoutContext('upgradePbxProjWithFlutterAssets', () async { - final File pbxprojFile = MemoryFileSystem.test().file('project.pbxproj') + final FakeIosProject project = FakeIosProject(fileSystem: MemoryFileSystem.test()); + final File pbxprojFile = project.xcodeProjectInfoFile + ..createSync(recursive: true) ..writeAsStringSync(flutterAssetPbxProjLines); - final FakeIosProject project = FakeIosProject(pbxprojFile); bool result = upgradePbxProjWithFlutterAssets(project, logger); expect(result, true); @@ -528,14 +780,88 @@ Could not build the precompiled application for the device.''', }); } +void createFakePlugins( + FlutterProject flutterProject, + FileSystem fileSystem, + List pluginNames, +) { + const String pluginYamlTemplate = ''' + flutter: + plugin: + platforms: + ios: + pluginClass: PLUGIN_CLASS + macos: + pluginClass: PLUGIN_CLASS + '''; + + final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache'); + final File packagesFile = flutterProject.directory.childFile('.packages') + ..createSync(recursive: true); + for (final String name in pluginNames) { + final Directory pluginDirectory = fakePubCache.childDirectory(name); + packagesFile.writeAsStringSync( + '$name:${pluginDirectory.childFile('lib').uri}\n', + mode: FileMode.writeOnlyAppend); + pluginDirectory.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name)); + } +} + class FakeIosProject extends Fake implements IosProject { - FakeIosProject(this.xcodeProjectInfoFile); + FakeIosProject({ + required MemoryFileSystem fileSystem, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios'); + + @override + Directory hostAppRoot; + @override - final File xcodeProjectInfoFile; + File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); @override Future hostAppBundleName(BuildInfo? buildInfo) async => 'UnitTestRunner.app'; @override - Directory get xcodeProject => xcodeProjectInfoFile.fileSystem.directory('Runner.xcodeproj'); + Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj'); + + @override + File get podfile => hostAppRoot.childFile('Podfile'); +} + +class FakeFlutterProject extends Fake implements FlutterProject { + FakeFlutterProject({ + required this.fileSystem, + this.usesSwiftPackageManager = false, + this.isModule = false, + }); + + MemoryFileSystem fileSystem; + + @override + late final Directory directory = fileSystem.directory('app_name'); + + @override + late FlutterManifest manifest; + + @override + File get flutterPluginsFile => directory.childFile('.flutter-plugins'); + + @override + File get flutterPluginsDependenciesFile => directory.childFile('.flutter-plugins-dependencies'); + + @override + late final IosProject ios = FakeIosProject(fileSystem: fileSystem); + + @override + final bool usesSwiftPackageManager; + + @override + final bool isModule; +} + +class FakeFlutterManifest extends Fake implements FlutterManifest { + @override + Set get dependencies => {}; } diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart new file mode 100644 index 000000000000..0a12510e8e6b --- /dev/null +++ b/packages/flutter_tools/test/general.shard/macos/cocoapod_utils_test.dart @@ -0,0 +1,555 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/flutter_manifest.dart'; +import 'package:flutter_tools/src/macos/cocoapod_utils.dart'; +import 'package:flutter_tools/src/macos/cocoapods.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; + +void main() { + group('processPodsIfNeeded', () { + late MemoryFileSystem fs; + late FakeCocoaPods cocoaPods; + late BufferLogger logger; + + // Adds basic properties to the flutterProject and its subprojects. + void setUpProject(FakeFlutterProject flutterProject, MemoryFileSystem fileSystem) { + flutterProject + ..manifest = FakeFlutterManifest() + ..directory = fileSystem.systemTempDirectory.childDirectory('app') + ..flutterPluginsFile = flutterProject.directory.childFile('.flutter-plugins') + ..flutterPluginsDependenciesFile = flutterProject.directory.childFile('.flutter-plugins-dependencies') + ..ios = FakeIosProject(fileSystem: fileSystem, parent: flutterProject) + ..macos = FakeMacOSProject(fileSystem: fileSystem, parent: flutterProject) + ..android = FakeAndroidProject() + ..web = FakeWebProject() + ..windows = FakeWindowsProject() + ..linux = FakeLinuxProject(); + flutterProject.directory.childFile('.packages').createSync(recursive: true); + } + + setUp(() async { + fs = MemoryFileSystem.test(); + cocoaPods = FakeCocoaPods(); + logger = BufferLogger.test(); + }); + + void createFakePlugins( + FlutterProject flutterProject, + FileSystem fileSystem, + List pluginNames, + ) { + const String pluginYamlTemplate = ''' + flutter: + plugin: + platforms: + ios: + pluginClass: PLUGIN_CLASS + macos: + pluginClass: PLUGIN_CLASS + '''; + + final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache'); + final File packagesFile = flutterProject.directory.childFile('.packages') + ..createSync(recursive: true); + for (final String name in pluginNames) { + final Directory pluginDirectory = fakePubCache.childDirectory(name); + packagesFile.writeAsStringSync( + '$name:${pluginDirectory.childFile('lib').uri}\n', + mode: FileMode.writeOnlyAppend); + pluginDirectory.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name)); + } + } + + group('for iOS', () { + group('using CocoaPods only', () { + testUsingContext('processes when there are plugins', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('processes when no plugins but the project is a module and podfile exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + flutterProject.isModule = true; + flutterProject.ios.podfile.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext("skips when no plugins and the project is a module but podfile doesn't exist", () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + flutterProject.isModule = true; + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('skips when no plugins and project is not a module', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + }); + + group('using Swift Package Manager', () { + testUsingContext('processes if podfile exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + flutterProject.ios.podfile.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('skip if podfile does not exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('process if podfile does not exists but forceCocoaPodsOnly is true', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + flutterProject.ios.flutterPluginSwiftPackageManifest.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.ios, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + forceCocoaPodsOnly: true, + ); + expect(cocoaPods.processedPods, isTrue); + expect(cocoaPods.podfileSetup, isTrue); + expect( + logger.warningText, + 'Swift Package Manager does not yet support this command. ' + 'CocoaPods will be used instead.\n'); + expect( + flutterProject.ios.flutterPluginSwiftPackageManifest.existsSync(), + isFalse, + ); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + Logger: () => logger, + }); + }); + }); + + group('for macOS', () { + group('using CocoaPods only', () { + testUsingContext('processes when there are plugins', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('processes when no plugins but the project is a module and podfile exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + flutterProject.isModule = true; + flutterProject.macos.podfile.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext("skips when no plugins and the project is a module but podfile doesn't exist", () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + flutterProject.isModule = true; + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('skips when no plugins and project is not a module', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + }); + + group('using Swift Package Manager', () { + testUsingContext('processes if podfile exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + flutterProject.macos.podfile.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isTrue); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('skip if podfile does not exists', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + ); + expect(cocoaPods.processedPods, isFalse); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + }); + + testUsingContext('process if podfile does not exists but forceCocoaPodsOnly is true', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject(); + setUpProject(flutterProject, fs); + createFakePlugins(flutterProject, fs, [ + 'plugin_one', + 'plugin_two' + ]); + flutterProject.usesSwiftPackageManager = true; + flutterProject.macos.flutterPluginSwiftPackageManifest.createSync(recursive: true); + + await processPodsIfNeeded( + flutterProject.macos, + fs.currentDirectory.childDirectory('build').path, + BuildMode.debug, + forceCocoaPodsOnly: true, + ); + expect(cocoaPods.processedPods, isTrue); + expect(cocoaPods.podfileSetup, isTrue); + expect( + logger.warningText, + 'Swift Package Manager does not yet support this command. ' + 'CocoaPods will be used instead.\n'); + expect( + flutterProject.macos.flutterPluginSwiftPackageManifest.existsSync(), + isFalse, + ); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + CocoaPods: () => cocoaPods, + Logger: () => logger, + }); + }); + }); + }); +} + +class FakeFlutterManifest extends Fake implements FlutterManifest { + @override + Set get dependencies => {}; +} + +class FakeFlutterProject extends Fake implements FlutterProject { + @override + bool isModule = false; + + @override + bool usesSwiftPackageManager = false; + + @override + late FlutterManifest manifest; + + @override + late Directory directory; + + @override + late File flutterPluginsFile; + + @override + late File flutterPluginsDependenciesFile; + + @override + late IosProject ios; + + @override + late MacOSProject macos; + + @override + late AndroidProject android; + + @override + late WebProject web; + + @override + late LinuxProject linux; + + @override + late WindowsProject windows; +} + +class FakeMacOSProject extends Fake implements MacOSProject { + FakeMacOSProject({ + required MemoryFileSystem fileSystem, + required this.parent, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios'); + + @override + String pluginConfigKey = 'macos'; + + @override + final FlutterProject parent; + + @override + Directory hostAppRoot; + + bool exists = true; + + @override + bool existsSync() => exists; + + @override + File get podfile => hostAppRoot.childFile('Podfile'); + + @override + File get xcodeProjectInfoFile => hostAppRoot + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + + @override + File get flutterPluginSwiftPackageManifest => hostAppRoot + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .childFile('Package.swift'); +} + +class FakeIosProject extends Fake implements IosProject { + FakeIosProject({ + required MemoryFileSystem fileSystem, + required this.parent, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios'); + + @override + String pluginConfigKey = 'ios'; + + @override + final FlutterProject parent; + + @override + Directory hostAppRoot; + + @override + bool exists = true; + + @override + bool existsSync() => exists; + + @override + File get podfile => hostAppRoot.childFile('Podfile'); + + @override + File get xcodeProjectInfoFile => hostAppRoot + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + + @override + File get flutterPluginSwiftPackageManifest => hostAppRoot + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .childFile('Package.swift'); +} + +class FakeAndroidProject extends Fake implements AndroidProject { + @override + String pluginConfigKey = 'android'; + + @override + bool existsSync() => false; +} + +class FakeWebProject extends Fake implements WebProject { + @override + String pluginConfigKey = 'web'; + + @override + bool existsSync() => false; +} + +class FakeWindowsProject extends Fake implements WindowsProject { + @override + String pluginConfigKey = 'windows'; + + @override + bool existsSync() => false; +} + +class FakeLinuxProject extends Fake implements LinuxProject { + @override + String pluginConfigKey = 'linux'; + + @override + bool existsSync() => false; +} + +class FakeCocoaPods extends Fake implements CocoaPods { + bool podfileSetup = false; + bool processedPods = false; + + @override + Future processPods({ + required XcodeBasedProject xcodeProject, + required BuildMode buildMode, + bool dependenciesChanged = true, + }) async { + processedPods = true; + return true; + } + + @override + Future setupPodfile(XcodeBasedProject xcodeProject) async { + podfileSetup = true; + } + + @override + void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {} +} diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index b1091b3e71c5..d25ad4cfec0e 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/flutter_plugins.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/cocoapods.dart'; @@ -457,6 +458,19 @@ void main() { expect(fakeProcessManager, hasNoRemainingExpectations); }); + testUsingContext("doesn't throw, if using Swift Package Manager and Podfile is missing.", () async { + final FlutterProject projectUnderTest = setupProjectUnderTest(); + final bool didInstall = await cocoaPodsUnderTest.processPods( + xcodeProject: projectUnderTest.ios, + buildMode: BuildMode.debug, + ); + expect(didInstall, isFalse); + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + testUsingContext('throws, if specs repo is outdated.', () async { final FlutterProject projectUnderTest = setupProjectUnderTest(); pretendPodIsInstalled(); @@ -1380,7 +1394,11 @@ Specs satisfying the `$fakePluginName (from `Flutter/ephemeral/.symlinks/plugins } class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { - FakeXcodeProjectInterpreter({this.isInstalled = true, this.buildSettings = const {}}); + FakeXcodeProjectInterpreter({ + this.isInstalled = true, + this.buildSettings = const {}, + this.version, + }); @override final bool isInstalled; @@ -1393,4 +1411,7 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete }) async => buildSettings; final Map buildSettings; + + @override + Version? version; } diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_validator_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_validator_test.dart index 670e32d25d83..2b72367e3b34 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_validator_test.dart @@ -16,18 +16,32 @@ void main() { final CocoaPodsValidator workflow = CocoaPodsValidator(FakeCocoaPods(CocoaPodsStatus.recommended, '1000.0.0'), UserMessages()); final ValidationResult result = await workflow.validate(); expect(result.type, ValidationType.success); + expect(result.messages.length, 1); + final ValidationMessage message = result.messages.first; + expect(message.type, ValidationMessageType.information); + expect(message.message, contains('CocoaPods version 1000.0.0')); }); testWithoutContext('Emits missing status when CocoaPods is not installed', () async { final CocoaPodsValidator workflow = CocoaPodsValidator(FakeCocoaPods(CocoaPodsStatus.notInstalled), UserMessages()); final ValidationResult result = await workflow.validate(); expect(result.type, ValidationType.missing); + expect(result.messages.length, 1); + final ValidationMessage message = result.messages.first; + expect(message.type, ValidationMessageType.error); + expect(message.message, contains('CocoaPods not installed')); + expect(message.message, contains('getting-started.html#installation')); }); testWithoutContext('Emits partial status when CocoaPods is installed with unknown version', () async { final CocoaPodsValidator workflow = CocoaPodsValidator(FakeCocoaPods(CocoaPodsStatus.unknownVersion), UserMessages()); final ValidationResult result = await workflow.validate(); expect(result.type, ValidationType.partial); + expect(result.messages.length, 1); + final ValidationMessage message = result.messages.first; + expect(message.type, ValidationMessageType.hint); + expect(message.message, contains('Unknown CocoaPods version installed')); + expect(message.message, contains('getting-started.html#updating-cocoapods')); }); testWithoutContext('Emits partial status when CocoaPods version is too low', () async { diff --git a/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart new file mode 100644 index 000000000000..ba539c3e1e87 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/macos/darwin_dependency_management_test.dart @@ -0,0 +1,692 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/macos/cocoapods.dart'; +import 'package:flutter_tools/src/macos/darwin_dependency_management.dart'; +import 'package:flutter_tools/src/macos/swift_package_manager.dart'; +import 'package:flutter_tools/src/platform_plugins.dart'; +import 'package:flutter_tools/src/plugins.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; + +void main() { + const List supportedPlatforms = [ + SupportedPlatform.ios, + SupportedPlatform.macos, + ]; + + group('DarwinDependencyManagement', () { + for (final SupportedPlatform platform in supportedPlatforms) { + group('for ${platform.name}', () { + group('generatePluginsSwiftPackage', () { + testWithoutContext('throw if invalid platform', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject(fileSystem: fs), + plugins: [], + cocoapods: FakeCocoaPods(), + swiftPackageManager: FakeSwiftPackageManager(), + fileSystem: fs, + logger: testLogger, + ); + + await expectLater(() => dependencyManagement.setUp( + platform: SupportedPlatform.android, + ), + throwsToolExit( + message: 'The platform android is incompatible with Darwin Dependency Managers. Only iOS and macOS are allowed.', + ), + ); + }); + group('when using Swift Package Manager', () { + testWithoutContext('with only CocoaPod plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'cocoapod_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginPodspecPath: cocoapodPluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isTrue); + }); + + testWithoutContext('with only Swift Package Manager plugins and no pod integration', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + }); + + testWithoutContext('with only Swift Package Manager plugins but project not migrated', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true); + projectPodfile.writeAsStringSync('Standard Podfile template'); + final FakeCocoaPods cocoaPods = FakeCocoaPods( + podFile: projectPodfile, + ); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ); + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.podfile.createSync(recursive: true); + xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + }); + + testWithoutContext('with only Swift Package Manager plugins with preexisting standard CocoaPods Podfile', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true); + projectPodfile.writeAsStringSync('Standard Podfile template'); + final FakeCocoaPods cocoaPods = FakeCocoaPods( + podFile: projectPodfile, + ); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ); + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.podfile.createSync(recursive: true); + xcodeProject.podfile.writeAsStringSync('Standard Podfile template'); + xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); + xcodeProject.xcodeProjectInfoFile.writeAsStringSync('FlutterGeneratedPluginSwiftPackage'); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + final String xcconfigPrefix = platform == SupportedPlatform.macos ? 'Flutter-' : ''; + expect(testLogger.warningText, contains( + 'All plugins found for ${platform.name} are Swift Packages, ' + 'but your project still has CocoaPods integration. To remove ' + 'CocoaPods integration, complete the following steps:\n' + ' * In the ${platform.name}/ directory run "pod deintegrate"\n' + ' * Also in the ${platform.name}/ directory, delete the Podfile\n' + ' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ' + 'in your ${platform.name}/Flutter/${xcconfigPrefix}Debug.xcconfig\n' + ' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ' + 'in your ${platform.name}/Flutter/${xcconfigPrefix}Release.xcconfig\n\n' + "Removing CocoaPods integration will improve the project's build time.\n" + )); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + }); + + testWithoutContext('with only Swift Package Manager plugins with preexisting custom CocoaPods Podfile', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final File projectPodfile = fs.file('/path/to/Podfile')..createSync(recursive: true); + projectPodfile.writeAsStringSync('Standard Podfile template'); + final FakeCocoaPods cocoaPods = FakeCocoaPods( + podFile: projectPodfile, + ); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ); + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.podfile.createSync(recursive: true); + xcodeProject.podfile.writeAsStringSync('Non-Standard Podfile template'); + xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); + xcodeProject.xcodeProjectInfoFile.writeAsStringSync('FlutterGeneratedPluginSwiftPackage'); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + final String xcconfigPrefix = platform == SupportedPlatform.macos ? 'Flutter-' : ''; + expect(testLogger.warningText, contains( + 'All plugins found for ${platform.name} are Swift Packages, ' + 'but your project still has CocoaPods integration. Your ' + 'project uses a non-standard Podfile and will need to be ' + 'migrated to Swift Package Manager manually. Some steps you ' + 'may need to complete include:\n' + ' * In the ${platform.name}/ directory run "pod deintegrate"\n' + ' * Transition any Pod dependencies to Swift Package equivalents. ' + 'See https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app\n' + ' * Transition any custom logic\n' + ' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ' + 'in your ${platform.name}/Flutter/${xcconfigPrefix}Debug.xcconfig\n' + ' * Remove the include to "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ' + 'in your ${platform.name}/Flutter/${xcconfigPrefix}Release.xcconfig\n\n' + "Removing CocoaPods integration will improve the project's build time.\n" + )); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + }); + + testWithoutContext('with mixed plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec') + ..createSync(recursive: true); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'cocoapod_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginPodspecPath: cocoapodPluginPodspec.path, + ), + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + FakePlugin( + name: 'neither_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isTrue); + }); + }); + + group('when not using Swift Package Manager', () { + testWithoutContext('but project already migrated', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'cocoapod_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginPodspecPath: cocoapodPluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + final FakeFlutterProject project = FakeFlutterProject( + usesSwiftPackageManager: true, + fileSystem: fs, + ); + final XcodeBasedProject xcodeProject = platform == SupportedPlatform.ios ? project.ios : project.macos; + xcodeProject.xcodeProjectInfoFile.createSync(recursive: true); + xcodeProject.xcodeProjectInfoFile.writeAsStringSync( + 'FlutterGeneratedPluginSwiftPackage', + ); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: project, + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isTrue); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isTrue); + }); + + testWithoutContext('with only CocoaPod plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'cocoapod_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginPodspecPath: cocoapodPluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + fileSystem: fs, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isFalse); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isTrue); + }); + + testWithoutContext('with only Swift Package Manager plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File swiftPackagePluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1/Package.swift') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'swift_package_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: swiftPackagePluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + fileSystem: fs, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await expectLater(() => dependencyManagement.setUp( + platform: platform, + ), + throwsToolExit( + message: 'Plugin swift_package_plugin_1 is only Swift Package Manager compatible. Try ' + 'enabling Swift Package Manager by running ' + '"flutter config --enable-swift-package-manager" or remove the ' + 'plugin as a dependency.', + ), + ); + expect(swiftPackageManager.generated, isFalse); + expect(cocoaPods.podfileSetup, isFalse); + }); + + testWithoutContext('when project is a module', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final File cocoapodPluginPodspec = fs.file('/path/to/cocoapod_plugin_1/darwin/cocoapod_plugin_1.podspec') + ..createSync(recursive: true); + final List plugins = [ + FakePlugin( + name: 'cocoapod_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginPodspecPath: cocoapodPluginPodspec.path, + ), + ]; + final FakeSwiftPackageManager swiftPackageManager = FakeSwiftPackageManager( + expectedPlugins: plugins, + ); + final FakeCocoaPods cocoaPods = FakeCocoaPods(); + + final DarwinDependencyManagement dependencyManagement = DarwinDependencyManagement( + project: FakeFlutterProject( + fileSystem: fs, + isModule: true, + ), + plugins: plugins, + cocoapods: cocoaPods, + swiftPackageManager: swiftPackageManager, + fileSystem: fs, + logger: testLogger, + ); + await dependencyManagement.setUp( + platform: platform, + ); + expect(swiftPackageManager.generated, isFalse); + expect(testLogger.warningText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(cocoaPods.podfileSetup, isFalse); + }); + + }); + }); + }); + } + }); +} + +class FakeIosProject extends Fake implements IosProject { + FakeIosProject({ + required MemoryFileSystem fileSystem, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios'); + + @override + Directory hostAppRoot; + + @override + File get podfile => hostAppRoot.childFile('Podfile'); + + @override + File get podfileLock => hostAppRoot.childFile('Podfile.lock'); + + @override + Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj'); + + @override + File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); + + @override + bool get flutterPluginSwiftPackageInProjectSettings { + return xcodeProjectInfoFile.existsSync() && + xcodeProjectInfoFile + .readAsStringSync() + .contains('FlutterGeneratedPluginSwiftPackage'); + } + + @override + Directory get managedDirectory => hostAppRoot.childDirectory('Flutter'); + + @override + File xcodeConfigFor(String mode) => managedDirectory.childFile('$mode.xcconfig'); +} + +class FakeMacOSProject extends Fake implements MacOSProject { + FakeMacOSProject({ + required MemoryFileSystem fileSystem, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory('macos'); + + @override + Directory hostAppRoot; + + @override + File get podfile => hostAppRoot.childFile('Podfile'); + + @override + File get podfileLock => hostAppRoot.childFile('Podfile.lock'); + + @override + Directory get xcodeProject => hostAppRoot.childDirectory('Runner.xcodeproj'); + + @override + File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); + + @override + bool get flutterPluginSwiftPackageInProjectSettings { + return xcodeProjectInfoFile.existsSync() && + xcodeProjectInfoFile + .readAsStringSync() + .contains('FlutterGeneratedPluginSwiftPackage'); + } + + @override + Directory get managedDirectory => hostAppRoot.childDirectory('Flutter'); + + @override + File xcodeConfigFor(String mode) => managedDirectory.childFile('Flutter-$mode.xcconfig'); +} + +class FakeFlutterProject extends Fake implements FlutterProject { + FakeFlutterProject({ + required this.fileSystem, + this.usesSwiftPackageManager = false, + this.isModule = false, + }); + + MemoryFileSystem fileSystem; + + + @override + late final IosProject ios = FakeIosProject(fileSystem: fileSystem); + + @override + late final MacOSProject macos = FakeMacOSProject(fileSystem: fileSystem); + + @override + final bool usesSwiftPackageManager; + + @override + final bool isModule; +} + +class FakeSwiftPackageManager extends Fake implements SwiftPackageManager { + FakeSwiftPackageManager({ + this.expectedPlugins, + }); + + bool generated = false; + final List? expectedPlugins; + + @override + Future generatePluginsSwiftPackage( + List plugins, + SupportedPlatform platform, + XcodeBasedProject project, + ) async { + generated = true; + expect(plugins, expectedPlugins); + } +} + +class FakeCocoaPods extends Fake implements CocoaPods { + FakeCocoaPods({ + this.podFile, + this.configIncludesPods = true, + }); + + File? podFile; + + bool podfileSetup = false; + bool addedPodDependencyToFlutterXcconfig = false; + bool configIncludesPods; + + @override + Future setupPodfile(XcodeBasedProject xcodeProject) async { + podfileSetup = true; + } + + @override + void addPodsDependencyToFlutterXcconfig(XcodeBasedProject xcodeProject) { + addedPodDependencyToFlutterXcconfig = true; + } + + @override + Future getPodfileTemplate(XcodeBasedProject xcodeProject, Directory runnerProject) async { + return podFile!; + } + + @override + bool xcconfigIncludesPods(File xcodeConfig) { + return configIncludesPods; + } + + @override + String includePodsXcconfig(String mode) { + return 'Pods/Target Support Files/Pods-Runner/Pods-Runner.${mode + .toLowerCase()}.xcconfig'; + } +} + +class FakePlugin extends Fake implements Plugin { + FakePlugin({ + required this.name, + required this.platforms, + String? pluginSwiftPackageManifestPath, + String? pluginPodspecPath, + }) : _pluginSwiftPackageManifestPath = pluginSwiftPackageManifestPath, + _pluginPodspecPath = pluginPodspecPath; + + final String? _pluginSwiftPackageManifestPath; + + final String? _pluginPodspecPath; + + @override + final String name; + + @override + final Map platforms; + + @override + String? pluginSwiftPackageManifestPath( + FileSystem fileSystem, + String platform, + ) { + return _pluginSwiftPackageManifestPath; + } + + @override + String? pluginPodspecPath( + FileSystem fileSystem, + String platform, + ) { + return _pluginPodspecPath; + } +} + +class FakePluginPlatform extends Fake implements PluginPlatform {} diff --git a/packages/flutter_tools/test/general.shard/macos/macos_project_migration_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_project_migration_test.dart index 0348ec3bdfed..027147a5cae0 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_project_migration_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/macos/migrations/flutter_application_migration.dart'; import 'package:flutter_tools/src/macos/migrations/macos_deployment_target_migration.dart'; +import 'package:flutter_tools/src/macos/migrations/nsapplicationmain_deprecation_migration.dart'; import 'package:flutter_tools/src/macos/migrations/remove_macos_framework_link_and_embedding_migration.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; @@ -400,6 +401,92 @@ platform :osx, '10.14' expect(testLogger.traceText, isEmpty); }); }); + + group('migrate @NSApplicationMain attribute to @main', () { + late MemoryFileSystem memoryFileSystem; + late BufferLogger testLogger; + late FakeMacOSProject project; + late File appDelegateFile; + + setUp(() { + memoryFileSystem = MemoryFileSystem(); + testLogger = BufferLogger.test(); + project = FakeMacOSProject(); + appDelegateFile = memoryFileSystem.file('AppDelegate.swift'); + project.appDelegateSwift = appDelegateFile; + }); + + testWithoutContext('skipped if files are missing', () async { + final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + expect(appDelegateFile.existsSync(), isFalse); + + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skipped if nothing to upgrade', () async { + const String appDelegateContents = ''' +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} +'''; + appDelegateFile.writeAsStringSync(appDelegateContents); + final DateTime lastModified = appDelegateFile.lastModifiedSync(); + + final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + + expect(appDelegateFile.lastModifiedSync(), lastModified); + expect(appDelegateFile.readAsStringSync(), appDelegateContents); + + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('updates AppDelegate.swift', () async { + appDelegateFile.writeAsStringSync(''' +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} +'''); + + final NSApplicationMainDeprecationMigration migration = NSApplicationMainDeprecationMigration( + project, + testLogger, + ); + await migration.migrate(); + + expect(appDelegateFile.readAsStringSync(), ''' +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} +'''); + expect(testLogger.warningText, contains('uses the deprecated @NSApplicationMain attribute, updating')); + }); + }); } class FakeMacOSProject extends Fake implements MacOSProject { @@ -411,4 +498,7 @@ class FakeMacOSProject extends Fake implements MacOSProject { @override File podfile = MemoryFileSystem.test().file('Podfile'); + + @override + File appDelegateSwift = MemoryFileSystem.test().file('AppDelegate.swift'); } diff --git a/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart b/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart new file mode 100644 index 000000000000..8eb11f017ed0 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/macos/swift_package_manager_test.dart @@ -0,0 +1,424 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/isolated/mustache_template.dart'; +import 'package:flutter_tools/src/macos/swift_package_manager.dart'; +import 'package:flutter_tools/src/platform_plugins.dart'; +import 'package:flutter_tools/src/plugins.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; + +const String _doubleIndent = ' '; + +void main() { + const List supportedPlatforms = [ + SupportedPlatform.ios, + SupportedPlatform.macos, + ]; + + group('SwiftPackageManager', () { + for (final SupportedPlatform platform in supportedPlatforms) { + group('for ${platform.name}', () { + + group('generatePluginsSwiftPackage', () { + testWithoutContext('throw if invalid platform', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + + final SwiftPackageManager spm = SwiftPackageManager( + fileSystem: fs, + templateRenderer: const MustacheTemplateRenderer(), + ); + + await expectLater(() => spm.generatePluginsSwiftPackage( + [], + SupportedPlatform.android, + project, + ), + throwsToolExit(message: 'The platform android is not compatible with Swift Package Manager. Only iOS and macOS are allowed.'), + ); + }); + + testWithoutContext('skip if no dependencies and not already migrated', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + + final SwiftPackageManager spm = SwiftPackageManager( + fileSystem: fs, + templateRenderer: const MustacheTemplateRenderer(), + ); + await spm.generatePluginsSwiftPackage( + [], + platform, + project, + ); + + expect(project.flutterPluginSwiftPackageManifest.existsSync(), isFalse); + }); + + testWithoutContext('generate if no dependencies and already migrated', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + project.xcodeProjectInfoFile.createSync(recursive: true); + project.xcodeProjectInfoFile.writeAsStringSync(''' +' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };'; +'''); + + final SwiftPackageManager spm = SwiftPackageManager( + fileSystem: fs, + templateRenderer: const MustacheTemplateRenderer(), + ); + await spm.generatePluginsSwiftPackage( + [], + platform, + project, + ); + + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue); + expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + $supportedPlatform + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ +$_doubleIndent + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage" + ) + ] +) +'''); + }); + + testWithoutContext('generate with single dependency', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + + final File validPlugin1Manifest = fs.file('/local/path/to/plugins/valid_plugin_1/Package.swift')..createSync(recursive: true); + final FakePlugin validPlugin1 = FakePlugin( + name: 'valid_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: validPlugin1Manifest.path, + ); + final SwiftPackageManager spm = SwiftPackageManager( + fileSystem: fs, + templateRenderer: const MustacheTemplateRenderer(), + ); + await spm.generatePluginsSwiftPackage( + [validPlugin1], + platform, + project, + ); + + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue); + expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + $supportedPlatform + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ + .package(name: "valid_plugin_1", path: "/local/path/to/plugins/valid_plugin_1") + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage", + dependencies: [ + .product(name: "valid-plugin-1", package: "valid_plugin_1") + ] + ) + ] +) +'''); + }); + + testWithoutContext('generate with multiple dependencies', () async { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + final FakePlugin nonPlatformCompatiblePlugin = FakePlugin( + name: 'invalid_plugin_due_to_incompatible_platform', + platforms: {}, + pluginSwiftPackageManifestPath: '/some/path', + ); + final FakePlugin pluginSwiftPackageManifestIsNull = FakePlugin( + name: 'invalid_plugin_due_to_null_plugin_swift_package_path', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: null, + ); + final FakePlugin pluginSwiftPackageManifestNotExists = FakePlugin( + name: 'invalid_plugin_due_to_plugin_swift_package_path_does_not_exist', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: '/some/path', + ); + + final File validPlugin1Manifest = fs.file('/local/path/to/plugins/valid_plugin_1/Package.swift')..createSync(recursive: true); + final FakePlugin validPlugin1 = FakePlugin( + name: 'valid_plugin_1', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: validPlugin1Manifest.path, + ); + final File validPlugin2Manifest = fs.file('/.pub-cache/plugins/valid_plugin_2/Package.swift')..createSync(recursive: true); + final FakePlugin validPlugin2 = FakePlugin( + name: 'valid_plugin_2', + platforms: {platform.name: FakePluginPlatform()}, + pluginSwiftPackageManifestPath: validPlugin2Manifest.path, + ); + + final SwiftPackageManager spm = SwiftPackageManager( + fileSystem: fs, + templateRenderer: const MustacheTemplateRenderer(), + ); + await spm.generatePluginsSwiftPackage( + [ + nonPlatformCompatiblePlugin, + pluginSwiftPackageManifestIsNull, + pluginSwiftPackageManifestNotExists, + validPlugin1, + validPlugin2, + ], + platform, + project, + ); + + final String supportedPlatform = platform == SupportedPlatform.ios + ? '.iOS("12.0")' + : '.macOS("10.14")'; + expect(project.flutterPluginSwiftPackageManifest.existsSync(), isTrue); + expect(project.flutterPluginSwiftPackageManifest.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + $supportedPlatform + ], + products: [ + .library(name: "FlutterGeneratedPluginSwiftPackage", type: .static, targets: ["FlutterGeneratedPluginSwiftPackage"]) + ], + dependencies: [ + .package(name: "valid_plugin_1", path: "/local/path/to/plugins/valid_plugin_1"), + .package(name: "valid_plugin_2", path: "/.pub-cache/plugins/valid_plugin_2") + ], + targets: [ + .target( + name: "FlutterGeneratedPluginSwiftPackage", + dependencies: [ + .product(name: "valid-plugin-1", package: "valid_plugin_1"), + .product(name: "valid-plugin-2", package: "valid_plugin_2") + ] + ) + ] +) +'''); + }); + }); + + group('updateMinimumDeployment', () { + testWithoutContext('return if invalid deploymentTarget', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + project.flutterPluginSwiftPackageManifest.createSync(recursive: true); + project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); + SwiftPackageManager.updateMinimumDeployment( + project: project, + platform: platform, + deploymentTarget: '', + ); + expect( + project.flutterPluginSwiftPackageManifest.readAsLinesSync(), + contains(supportedPlatform), + ); + }); + + testWithoutContext('return if deploymentTarget is lower than default', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + project.flutterPluginSwiftPackageManifest.createSync(recursive: true); + project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); + SwiftPackageManager.updateMinimumDeployment( + project: project, + platform: platform, + deploymentTarget: '9.0', + ); + expect( + project.flutterPluginSwiftPackageManifest.readAsLinesSync(), + contains(supportedPlatform), + ); + }); + + testWithoutContext('return if deploymentTarget is same than default', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + project.flutterPluginSwiftPackageManifest.createSync(recursive: true); + project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); + SwiftPackageManager.updateMinimumDeployment( + project: project, + platform: platform, + deploymentTarget: platform == SupportedPlatform.ios ? '12.0' : '10.14', + ); + expect( + project.flutterPluginSwiftPackageManifest.readAsLinesSync(), + contains(supportedPlatform), + ); + }); + + testWithoutContext('update if deploymentTarget is higher than default', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: fs, + ); + final String supportedPlatform = platform == SupportedPlatform.ios ? '.iOS("12.0")' : '.macOS("10.14")'; + project.flutterPluginSwiftPackageManifest.createSync(recursive: true); + project.flutterPluginSwiftPackageManifest.writeAsStringSync(supportedPlatform); + SwiftPackageManager.updateMinimumDeployment( + project: project, + platform: platform, + deploymentTarget: '14.0', + ); + expect( + project.flutterPluginSwiftPackageManifest + .readAsLinesSync() + .contains(supportedPlatform), + isFalse, + ); + expect( + project.flutterPluginSwiftPackageManifest.readAsLinesSync(), + contains(platform == SupportedPlatform.ios ? '.iOS("14.0")' : '.macOS("14.0")'), + ); + }); + }); + }); + } + }); +} + +class FakeXcodeProject extends Fake implements IosProject { + FakeXcodeProject({ + required MemoryFileSystem fileSystem, + required String platform, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory(platform); + + @override + Directory hostAppRoot; + + @override + Directory get xcodeProject => hostAppRoot.childDirectory('$hostAppProjectName.xcodeproj'); + + @override + File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); + + @override + String hostAppProjectName = 'Runner'; + + @override + Directory get flutterPluginSwiftPackageDirectory => hostAppRoot + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage'); + + @override + File get flutterPluginSwiftPackageManifest => + flutterPluginSwiftPackageDirectory.childFile('Package.swift'); + + @override + bool get flutterPluginSwiftPackageInProjectSettings { + return xcodeProjectInfoFile.existsSync() && + xcodeProjectInfoFile + .readAsStringSync() + .contains('FlutterGeneratedPluginSwiftPackage'); + } +} + +class FakePlugin extends Fake implements Plugin { + FakePlugin({ + required this.name, + required this.platforms, + required String? pluginSwiftPackageManifestPath, + }) : _pluginSwiftPackageManifestPath = pluginSwiftPackageManifestPath; + + final String? _pluginSwiftPackageManifestPath; + + @override + final String name; + + @override + final Map platforms; + + @override + String? pluginSwiftPackageManifestPath( + FileSystem fileSystem, + String platform, + ) { + return _pluginSwiftPackageManifestPath; + } +} + +class FakePluginPlatform extends Fake implements PluginPlatform {} diff --git a/packages/flutter_tools/test/general.shard/macos/swift_packages_test.dart b/packages/flutter_tools/test/general.shard/macos/swift_packages_test.dart new file mode 100644 index 000000000000..89e6674496a9 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/macos/swift_packages_test.dart @@ -0,0 +1,375 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/version.dart'; +import 'package:flutter_tools/src/isolated/mustache_template.dart'; +import 'package:flutter_tools/src/macos/swift_packages.dart'; + +import '../../src/common.dart'; + +const String _doubleIndent = ' '; + +void main() { + group('SwiftPackage', () { + testWithoutContext('createSwiftPackage also creates source file for each default target', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + const String target1Name = 'Target1'; + const String target2Name = 'Target2'; + final File target1SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift'); + final File target2SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift'); + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [], + products: [], + dependencies: [], + targets: [ + SwiftPackageTarget.defaultTarget(name: target1Name), + SwiftPackageTarget.defaultTarget(name: 'Target2'), + ], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.existsSync(), isTrue); + expect(target1SourceFile.existsSync(), isTrue); + expect(target2SourceFile.existsSync(), isTrue); + }); + + testWithoutContext('createSwiftPackage also creates source file for binary target', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [], + products: [], + dependencies: [], + targets: [ + SwiftPackageTarget.binaryTarget(name: 'BinaryTarget', relativePath: ''), + ], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.existsSync(), isTrue); + expect(fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/BinaryTarget/BinaryTarget.swift').existsSync(), isFalse); + }); + + testWithoutContext('createSwiftPackage does not creates source file if already exists', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + const String target1Name = 'Target1'; + const String target2Name = 'Target2'; + final File target1SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/$target1Name.swift'); + final File target2SourceFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/$target2Name.swift'); + + fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target1Name/SomeSourceFile.swift').createSync(recursive: true); + fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Sources/$target2Name/SomeSourceFile.swift').createSync(recursive: true); + + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [], + products: [], + dependencies: [], + targets: [ + SwiftPackageTarget.defaultTarget(name: target1Name), + SwiftPackageTarget.defaultTarget(name: 'Target2'), + ], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.existsSync(), isTrue); + expect(target1SourceFile.existsSync(), isFalse); + expect(target2SourceFile.existsSync(), isFalse); + }); + + group('create Package.swift from template', () { + testWithoutContext('with none in each field', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [], + products: [], + dependencies: [], + targets: [], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + products: [ +$_doubleIndent + ], + dependencies: [ +$_doubleIndent + ], + targets: [ +$_doubleIndent + ] +) +'''); + }); + + testWithoutContext('with single in each field', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [ + SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.ios, version: Version(12, 0, null)), + ], + products: [ + SwiftPackageProduct(name: 'Product1', targets: ['Target1']), + ], + dependencies: [ + SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'), + ], + targets: [ + SwiftPackageTarget.defaultTarget( + name: 'Target1', + dependencies: [ + SwiftPackageTargetDependency.product(name: 'TargetDependency1', packageName: 'TargetDependency1Package'), + ], + ) + ], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "Product1", targets: ["Target1"]) + ], + dependencies: [ + .package(name: "Dependency1", path: "/path/to/dependency1") + ], + targets: [ + .target( + name: "Target1", + dependencies: [ + .product(name: "TargetDependency1", package: "TargetDependency1Package") + ] + ) + ] +) +'''); + }); + + testWithoutContext('with multiple in each field', () { + final MemoryFileSystem fs = MemoryFileSystem(); + final File swiftPackageFile = fs.systemTempDirectory.childFile('Packages/FlutterGeneratedPluginSwiftPackage/Package.swift'); + final SwiftPackage swiftPackage = SwiftPackage( + manifest: swiftPackageFile, + name: 'FlutterGeneratedPluginSwiftPackage', + platforms: [ + SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.ios, version: Version(12, 0, null)), + SwiftPackageSupportedPlatform(platform: SwiftPackagePlatform.macos, version: Version(10, 14, null)), + ], + products: [ + SwiftPackageProduct(name: 'Product1', targets: ['Target1']), + SwiftPackageProduct(name: 'Product2', targets: ['Target2']) + ], + dependencies: [ + SwiftPackagePackageDependency(name: 'Dependency1', path: '/path/to/dependency1'), + SwiftPackagePackageDependency(name: 'Dependency2', path: '/path/to/dependency2'), + ], + targets: [ + SwiftPackageTarget.binaryTarget(name: 'Target1', relativePath: '/path/to/target1'), + SwiftPackageTarget.defaultTarget( + name: 'Target2', + dependencies: [ + SwiftPackageTargetDependency.target(name: 'TargetDependency1'), + SwiftPackageTargetDependency.product(name: 'TargetDependency2', packageName: 'TargetDependency2Package'), + ], + ) + ], + templateRenderer: const MustacheTemplateRenderer(), + ); + swiftPackage.createSwiftPackage(); + expect(swiftPackageFile.readAsStringSync(), ''' +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. +// +// Generated file. Do not edit. +// + +import PackageDescription + +let package = Package( + name: "FlutterGeneratedPluginSwiftPackage", + platforms: [ + .iOS("12.0"), + .macOS("10.14") + ], + products: [ + .library(name: "Product1", targets: ["Target1"]), + .library(name: "Product2", targets: ["Target2"]) + ], + dependencies: [ + .package(name: "Dependency1", path: "/path/to/dependency1"), + .package(name: "Dependency2", path: "/path/to/dependency2") + ], + targets: [ + .binaryTarget( + name: "Target1", + path: "/path/to/target1" + ), + .target( + name: "Target2", + dependencies: [ + .target(name: "TargetDependency1"), + .product(name: "TargetDependency2", package: "TargetDependency2Package") + ] + ) + ] +) +'''); + }); + }); + }); + + testWithoutContext('Format SwiftPackageSupportedPlatform', () { + final SwiftPackageSupportedPlatform supportedPlatform = SwiftPackageSupportedPlatform( + platform: SwiftPackagePlatform.ios, + version: Version(17, 0, null), + ); + expect(supportedPlatform.format(), '.iOS("17.0")'); + }); + + group('Format SwiftPackageProduct', () { + testWithoutContext('without targets and libraryType', () { + final SwiftPackageProduct product = SwiftPackageProduct( + name: 'ProductName', + targets: [], + ); + expect(product.format(), '.library(name: "ProductName")'); + }); + + testWithoutContext('with targets', () { + final SwiftPackageProduct singleProduct = SwiftPackageProduct( + name: 'ProductName', + targets: ['Target1'], + ); + expect(singleProduct.format(), '.library(name: "ProductName", targets: ["Target1"])'); + + final SwiftPackageProduct multipleProducts = SwiftPackageProduct( + name: 'ProductName', + targets: ['Target1', 'Target2'], + ); + expect(multipleProducts.format(), '.library(name: "ProductName", targets: ["Target1", "Target2"])'); + }); + + testWithoutContext('with libraryType', () { + final SwiftPackageProduct product = SwiftPackageProduct( + name: 'ProductName', + targets: [], + libraryType: SwiftPackageLibraryType.dynamic, + ); + expect(product.format(), '.library(name: "ProductName", type: .dynamic)'); + }); + + testWithoutContext('with targets and libraryType', () { + final SwiftPackageProduct product = SwiftPackageProduct( + name: 'ProductName', + targets: ['Target1', 'Target2'], + libraryType: SwiftPackageLibraryType.dynamic, + ); + expect(product.format(), '.library(name: "ProductName", type: .dynamic, targets: ["Target1", "Target2"])'); + }); + }); + + testWithoutContext('Format SwiftPackagePackageDependency', () { + final SwiftPackagePackageDependency supportedPlatform = SwiftPackagePackageDependency( + name: 'DependencyName', + path: '/path/to/dependency', + ); + expect(supportedPlatform.format(), '.package(name: "DependencyName", path: "/path/to/dependency")'); + }); + + group('Format SwiftPackageTarget', () { + testWithoutContext('as default target with multiple SwiftPackageTargetDependency', () { + final SwiftPackageTarget product = SwiftPackageTarget.defaultTarget( + name: 'ProductName', + dependencies: [ + SwiftPackageTargetDependency.target(name: 'Dependency1'), + SwiftPackageTargetDependency.product(name: 'Dependency2', packageName: 'Dependency2Package'), + ], + ); + expect(product.format(), ''' +.target( + name: "ProductName", + dependencies: [ + .target(name: "Dependency1"), + .product(name: "Dependency2", package: "Dependency2Package") + ] + )'''); + }); + + testWithoutContext('as default target with no SwiftPackageTargetDependency', () { + final SwiftPackageTarget product = SwiftPackageTarget.defaultTarget( + name: 'ProductName', + ); + expect(product.format(), ''' +.target( + name: "ProductName" + )'''); + }); + + testWithoutContext('as binaryTarget', () { + final SwiftPackageTarget product = SwiftPackageTarget.binaryTarget( + name: 'ProductName', + relativePath: '/path/to/target', + ); + expect(product.format(), ''' +.binaryTarget( + name: "ProductName", + path: "/path/to/target" + )'''); + }); + }); + + group('Format SwiftPackageTargetDependency', () { + testWithoutContext('with only name', () { + final SwiftPackageTargetDependency targetDependency = SwiftPackageTargetDependency.target( + name: 'DependencyName', + ); + expect(targetDependency.format(), ' .target(name: "DependencyName")'); + }); + + testWithoutContext('with name and package', () { + final SwiftPackageTargetDependency targetDependency = SwiftPackageTargetDependency.product( + name: 'DependencyName', + packageName: 'PackageName', + ); + expect(targetDependency.format(), ' .product(name: "DependencyName", package: "PackageName")'); + }); + }); +} diff --git a/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart new file mode 100644 index 000000000000..74f60ec85d85 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/migrations/swift_package_manager_integration_migration_test.dart @@ -0,0 +1,3270 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/ios/plist_parser.dart'; +import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/migrations/swift_package_manager_integration_migration.dart'; + +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; + +const List supportedPlatforms = [ + SupportedPlatform.ios, + SupportedPlatform.macos +]; + +void main() { + group('Flutter Package Migration', () { + testWithoutContext('fails if Xcode project not found', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ), + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Xcode project not found.'), + ); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + group('get scheme file', () { + testWithoutContext('fails if Xcode project info not found', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project._projectInfo = null; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to get Xcode project info.'), + ); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('fails if Xcode workspace not found', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeWorkspace = null; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Xcode workspace not found.'), + ); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('fails if scheme not found', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project._projectInfo = XcodeProjectInfo( + [], + [], + [], + testLogger, + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'You must specify a --flavor option to select one of the available schemes.'), + ); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('fails if scheme file not found', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles( + project, + SupportedPlatform.ios, + createSchemeFile: false, + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to get scheme file for Runner.'), + ); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + }); + + testWithoutContext('does not migrate if already migrated', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildActions(SupportedPlatform.ios, hasFrameworkScript: true), + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(_allSectionsMigrated(SupportedPlatform.ios)), + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await projectMigration.migrate(); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + expect(testLogger.warningText, isEmpty); + expect(testLogger.errorText, isEmpty); + }); + + group('migrate scheme', () { + testWithoutContext('skipped if already updated', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildActions(SupportedPlatform.ios, hasFrameworkScript: true), + ); + + project.xcodeProjectInfoFile.writeAsStringSync(''); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(SupportedPlatform.ios), + ]; + settingsAsJsonBeforeMigration.removeAt(_buildFileSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater(() => projectMigration.migrate(), throwsToolExit()); + expect( + testLogger.traceText, + contains('Runner.xcscheme already migrated. Skipping...'), + ); + }); + + for (final SupportedPlatform platform in supportedPlatforms) { + group('for ${platform.name}', () { + testWithoutContext('fails if scheme is missing BlueprintIdentifier for Runner native target', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync(''); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Could not find BuildableReference'), + ); + }); + + testWithoutContext('fails if BuildableName does not follow BlueprintIdentifier in scheme', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync(''' + + +''' + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Could not find BuildableName'), + ); + }); + + testWithoutContext('fails if BlueprintName does not follow BuildableName in scheme', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync(''' + + +''' + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Could not find BlueprintName'), + ); + }); + + testWithoutContext('fails if ReferencedContainer does not follow BlueprintName in scheme', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync(''' + + +''' + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Could not find ReferencedContainer'), + ); + }); + + testWithoutContext('fails if cannot find BuildActionEntries in scheme', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildableReference(platform), + ); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Could not find BuildActionEntries'), + ); + }); + + testWithoutContext('fails if updated scheme is not valid xml', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync( + '${_validBuildActions(platform)} ', + ); + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse Runner.xcscheme: Invalid xml:'), + ); + }); + + testWithoutContext('successfully updates scheme with preexisting PreActions', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildActions(platform, hasPreActions: true), + ); + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsMigratedAsJson(platform)), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + + await projectMigration.migrate(); + expect( + project.xcodeProjectSchemeFile().readAsStringSync(), + _validBuildActions(platform, hasFrameworkScript: true), + ); + }); + + testWithoutContext('successfully updates scheme with no preexisting PreActions', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildActions(platform), + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsMigratedAsJson(platform)), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + + await projectMigration.migrate(); + expect( + project.xcodeProjectSchemeFile().readAsStringSync(), + _validBuildActions(platform, hasFrameworkScript: true), + ); + }); + }); + } + }); + + group('migrate pbxproj', () { + testWithoutContext('skipped if already updated', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(_allSectionsMigrated(SupportedPlatform.ios)), + ); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(SupportedPlatform.ios), + ]; + settingsAsJsonBeforeMigration.removeAt(_buildFileSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await projectMigration.migrate(); + expect( + testLogger.traceText, + contains('project.pbxproj already migrated. Skipping...'), + ); + }); + + group('fails if parsing project.pbxproj', () { + testWithoutContext('fails plutil command', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Failed to parse project settings.'), + ); + }); + + testWithoutContext('returns unexpected JSON', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(json: '[]'), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'project.pbxproj returned unexpected JSON response'), + ); + }); + + testWithoutContext('returns non-JSON', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(json: 'this is not json'), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'project.pbxproj returned non-JSON response'), + ); + }); + }); + + group('fails if duplicate id', () { + testWithoutContext('for PBXBuildFile', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeProjectInfoFile.writeAsStringSync('78A318202AECB46A00862997'); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Duplicate id found for PBXBuildFile'), + ); + }); + + testWithoutContext('for XCSwiftPackageProductDependency', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeProjectInfoFile.writeAsStringSync('78A3181F2AECB46A00862997'); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Duplicate id found for XCSwiftPackageProductDependency'), + ); + }); + + testWithoutContext('for XCLocalSwiftPackageReference', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, SupportedPlatform.ios); + project.xcodeProjectInfoFile.writeAsStringSync('781AD8BC2B33823900A9FFBB'); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + SupportedPlatform.ios, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser(), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Duplicate id found for XCLocalSwiftPackageReference'), + ); + }); + }); + + for (final SupportedPlatform platform in supportedPlatforms) { + group('for ${platform.name}', () { + group('migrate PBXBuildFile', () { + testWithoutContext('fails if missing Begin PBXBuildFile section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput([]), + ), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXBuildFile section'), + ); + }); + + testWithoutContext('fails if missing End PBXBuildFile section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_buildFileSectionIndex] = ''' +/* Begin PBXBuildFile section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_buildFileSectionIndex] = unmigratedBuildFileSectionAsJson; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput([]), + ), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find end of PBXBuildFile section'), + ); + }); + + testWithoutContext('fails if End before Begin for PBXBuildFile section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_buildFileSectionIndex] = ''' +/* End PBXBuildFile section */ +/* Begin PBXBuildFile section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_buildFileSectionIndex] = unmigratedBuildFileSectionAsJson; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput([]), + ), + ); + expect( + () => projectMigration.migrate(), + throwsToolExit(message: 'Found the end of PBXBuildFile section before the beginning.'), + ); + }); + + testWithoutContext('successfully added', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_buildFileSectionIndex] = unmigratedBuildFileSection; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_buildFileSectionIndex] = unmigratedBuildFileSectionAsJson; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXBuildFile already migrated. Skipping...'), + isFalse, + ); + settingsBeforeMigration[_buildFileSectionIndex] = migratedBuildFileSection; + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(settingsBeforeMigration), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + group('migrate PBXFrameworksBuildPhase', () { + testWithoutContext('fails if missing PBXFrameworksBuildPhase section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_frameworksBuildPhaseSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_frameworksBuildPhaseSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXFrameworksBuildPhase section'), + ); + }); + + testWithoutContext('fails if missing Runner target subsection following PBXFrameworksBuildPhase begin header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_frameworksBuildPhaseSectionIndex] = ''' +/* Begin PBXFrameworksBuildPhase section */ +/* End PBXFrameworksBuildPhase section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_frameworksBuildPhaseSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXFrameworksBuildPhase for Runner target'), + ); + }); + + testWithoutContext('fails if missing Runner target subsection before PBXFrameworksBuildPhase end header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_frameworksBuildPhaseSectionIndex] = ''' +/* Begin PBXFrameworksBuildPhase section */ +/* End PBXFrameworksBuildPhase section */ +/* Begin NonExistant section */ + ${_runnerFrameworksBuildPhaseIdentifer(platform)} /* Frameworks */ = { + }; +/* End NonExistant section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_frameworksBuildPhaseSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXFrameworksBuildPhase for Runner target'), + ); + }); + + testWithoutContext('fails if missing Runner target in parsed settings', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_frameworksBuildPhaseSectionIndex] = unmigratedFrameworksBuildPhaseSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_frameworksBuildPhaseSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find parsed PBXFrameworksBuildPhase for Runner target'), + ); + }); + + testWithoutContext('successfully added when files field is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_frameworksBuildPhaseSectionIndex] = unmigratedFrameworksBuildPhaseSection( + platform, + missingFiles: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_frameworksBuildPhaseSectionIndex] = unmigratedFrameworksBuildPhaseSectionAsJson( + platform, + missingFiles: true, + ); + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_frameworksBuildPhaseSectionIndex] = migratedFrameworksBuildPhaseSection( + platform, + missingFiles: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXFrameworksBuildPhase already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when files field is empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXFrameworksBuildPhase already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when files field is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_frameworksBuildPhaseSectionIndex] = unmigratedFrameworksBuildPhaseSection( + platform, + withCocoapods: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_frameworksBuildPhaseSectionIndex] = unmigratedFrameworksBuildPhaseSectionAsJson( + platform, + withCocoapods: true, + ); + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_frameworksBuildPhaseSectionIndex] = migratedFrameworksBuildPhaseSection( + platform, + withCocoapods: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXFrameworksBuildPhase already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + group('migrate PBXNativeTarget', () { + testWithoutContext('fails if missing PBXNativeTarget section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_nativeTargetSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_nativeTargetSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXNativeTarget section'), + ); + }); + + testWithoutContext('fails if missing Runner target in parsed settings', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = unmigratedNativeTargetSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_nativeTargetSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find parsed PBXNativeTarget for Runner target'), + ); + }); + + testWithoutContext('fails if missing Runner target subsection following PBXNativeTarget begin header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = ''' +/* Begin PBXNativeTarget section */ +/* End PBXNativeTarget section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXNativeTarget for Runner target'), + ); + }); + + testWithoutContext('fails if missing Runner target subsection before PBXNativeTarget end header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = ''' +/* Begin PBXNativeTarget section */ +/* End PBXNativeTarget section */ +/* Begin NonExistant section */ + ${_runnerNativeTargetIdentifer(platform)} /* Runner */ = { + }; +/* End NonExistant section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXNativeTarget for Runner target'), + ); + }); + + testWithoutContext('successfully added when packageProductDependencies field is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = unmigratedNativeTargetSection( + platform, + missingPackageProductDependencies: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_nativeTargetSectionIndex] = unmigratedNativeTargetSectionAsJson( + platform, + missingPackageProductDependencies: true, + ); + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_nativeTargetSectionIndex] = migratedNativeTargetSection( + platform, + missingPackageProductDependencies: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXNativeTarget already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when packageProductDependencies field is empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = unmigratedNativeTargetSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXNativeTarget already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when packageProductDependencies field is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_nativeTargetSectionIndex] = unmigratedNativeTargetSection( + platform, + withOtherDependency: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_nativeTargetSectionIndex] = migratedNativeTargetSection( + platform, + withOtherDependency: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXNativeTarget already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + group('migrate PBXProject', () { + testWithoutContext('fails if missing PBXProject section', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_projectSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_projectSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find beginning of PBXProject section'), + ); + }); + + testWithoutContext('fails if missing Runner project subsection following PBXProject begin header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = ''' +/* Begin PBXProject section */ +/* End PBXProject section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_projectSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXProject for Runner'), + ); + }); + + testWithoutContext('fails if missing Runner project subsection before PBXProject end header', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = ''' +/* Begin PBXProject section */ +/* End PBXProject section */ +/* Begin NonExistant section */ + ${_projectIdentifier(platform)} /* Project object */ = { + }; +/* End NonExistant section */ +'''; + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_projectSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find PBXProject for Runner'), + ); + }); + + testWithoutContext('fails if missing Runner project in parsed settings', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = unmigratedProjectSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_projectSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find parsed PBXProject for Runner'), + ); + }); + + testWithoutContext('successfully added when packageReferences field is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = unmigratedProjectSection( + platform, + missingPackageReferences: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration[_projectSectionIndex] = unmigratedProjectSectionAsJson( + platform, + missingPackageReferences: true, + ); + + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_projectSectionIndex] = migratedProjectSection( + platform, + missingPackageReferences: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXProject already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when packageReferences field is empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = unmigratedProjectSection(platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXProject already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when packageReferences field is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_projectSectionIndex] = unmigratedProjectSection( + platform, + withOtherReference: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_projectSectionIndex] = migratedProjectSection( + platform, + withOtherReference: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('PBXProject already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + group('migrate XCLocalSwiftPackageReference', () { + testWithoutContext('fails if unable to find section to append it after', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_localSwiftPackageReferenceSectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find any sections'), + ); + }); + + testWithoutContext('successfully added when section is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_localSwiftPackageReferenceSectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings.removeAt(_localSwiftPackageReferenceSectionIndex); + expectedSettings.add(migratedLocalSwiftPackageReferenceSection()); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCLocalSwiftPackageReference already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when section is empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_localSwiftPackageReferenceSectionIndex] = unmigratedLocalSwiftPackageReferenceSection(); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCLocalSwiftPackageReference already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when section is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_localSwiftPackageReferenceSectionIndex] = unmigratedLocalSwiftPackageReferenceSection( + withOtherReference: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_localSwiftPackageReferenceSectionIndex] = migratedLocalSwiftPackageReferenceSection( + withOtherReference: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCLocalSwiftPackageReference already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + group('migrate XCSwiftPackageProductDependency', () { + testWithoutContext('fails if unable to find section to append it after', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsMigratedAsJson(platform), + ]; + settingsAsJsonBeforeMigration.removeAt(_swiftPackageProductDependencySectionIndex); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: FakePlistParser( + json: _plutilOutput(settingsAsJsonBeforeMigration), + ), + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to find any sections'), + ); + }); + + testWithoutContext('successfully added when section is missing', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration.removeAt(_swiftPackageProductDependencySectionIndex); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCSwiftPackageProductDependency already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when section is empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_swiftPackageProductDependencySectionIndex] = unmigratedSwiftPackageProductDependencySection(); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCSwiftPackageProductDependency already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(_allSectionsMigrated(platform)), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + + testWithoutContext('successfully added when section is not empty', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final List settingsBeforeMigration = [ + ..._allSectionsUnmigrated(platform), + ]; + settingsBeforeMigration[_swiftPackageProductDependencySectionIndex] = unmigratedSwiftPackageProductDependencySection( + withOtherDependency: true, + ); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(settingsBeforeMigration), + ); + final List settingsAsJsonBeforeMigration = [ + ..._allSectionsUnmigratedAsJson(platform), + ]; + final List expectedSettings = [ + ..._allSectionsMigrated(platform), + ]; + expectedSettings[_swiftPackageProductDependencySectionIndex] = migratedSwiftPackageProductDependencySection( + withOtherDependency: true, + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(settingsAsJsonBeforeMigration), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await projectMigration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + testLogger.traceText.contains('XCSwiftPackageProductDependency already migrated. Skipping...'), + isFalse, + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + _projectSettings(expectedSettings), + ); + expect(plistParser.hasRemainingExpectations, isFalse); + }); + }); + + testWithoutContext('throw if settings not updated correctly', () async{ + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(_allSectionsUnmigrated(platform)), + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsUnmigratedAsJson(platform)), + _plutilOutput(_allSectionsUnmigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Settings were not updated correctly.'), + ); + expect( + testLogger.errorText, + contains('PBXBuildFile was not migrated or was migrated incorrectly.'), + ); + expect( + testLogger.errorText, + contains('PBXFrameworksBuildPhase was not migrated or was migrated incorrectly.'), + ); + expect( + testLogger.errorText, + contains('PBXNativeTarget was not migrated or was migrated incorrectly.'), + ); + expect( + testLogger.errorText, + contains('PBXProject was not migrated or was migrated incorrectly.'), + ); + expect( + testLogger.errorText, + contains('XCLocalSwiftPackageReference was not migrated or was migrated incorrectly.'), + ); + expect( + testLogger.errorText, + contains('XCSwiftPackageProductDependency was not migrated or was migrated incorrectly.'), + ); + }); + }); + } + }); + + group('validate project settings', () { + testWithoutContext('throw if settings fail to compile', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + const SupportedPlatform platform = SupportedPlatform.ios; + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(_allSectionsUnmigrated(platform)), + ); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsUnmigratedAsJson(platform)), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final SwiftPackageManagerIntegrationMigration projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter( + throwErrorOnGetInfo: true, + ), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + ); + await expectLater( + () => projectMigration.migrate(), + throwsToolExit(message: 'Unable to get Xcode project information'), + ); + }); + + testWithoutContext('restore project settings from backup on failure', () async { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final BufferLogger testLogger = BufferLogger.test(); + const SupportedPlatform platform = SupportedPlatform.ios; + final FakeXcodeProject project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform); + + final String originalProjectInfo = _projectSettings( + _allSectionsUnmigrated(platform), + ); + project.xcodeProjectInfoFile.writeAsStringSync(originalProjectInfo); + final String originalSchemeContents = _validBuildActions(platform); + + final FakePlistParser plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsUnmigratedAsJson(platform)), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + + final FakeSwiftPackageManagerIntegrationMigration projectMigration = FakeSwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter( + throwErrorOnGetInfo: true, + ), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + validateBackup: true, + ); + await expectLater( + () async => projectMigration.migrate(), + throwsToolExit(), + ); + expect( + testLogger.traceText, + contains('Restoring project settings from backup file...'), + ); + expect( + project.xcodeProjectInfoFile.readAsStringSync(), + originalProjectInfo, + ); + expect( + project.xcodeProjectSchemeFile().readAsStringSync(), + originalSchemeContents, + ); + }); + }); + }); +} + +void _createProjectFiles( + FakeXcodeProject project, + SupportedPlatform platform, { + bool createSchemeFile = true, + String? scheme, +}) { + project.parent.directory.createSync(recursive: true); + project.hostAppRoot.createSync(recursive: true); + project.xcodeProjectInfoFile.createSync(recursive: true); + if (createSchemeFile) { + project.xcodeProjectSchemeFile(scheme: scheme).createSync(recursive: true); + project.xcodeProjectSchemeFile().writeAsStringSync( + _validBuildActions(platform), + ); + } +} + +String _validBuildActions( + SupportedPlatform platform, { + bool hasPreActions = false, + bool hasFrameworkScript = false, +}) { + final String scriptText; + if (platform == SupportedPlatform.ios) { + scriptText = r'scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">'; + } else { + scriptText = r'scriptText = ""$FLUTTER_ROOT"/packages/flutter_tools/bin/macos_assemble.sh prepare ">'; + } + String preActions = ''; + if (hasFrameworkScript) { + preActions = ''' +\n + + + + + + + + '''; + } else if (hasPreActions) { + preActions = ''' +\n + '''; + } + return ''' + $preActions + + +${_validBuildableReference(platform)} + + + +'''; +} + +String _validBuildableReference(SupportedPlatform platform) { + return ''' + + '''; +} + +const int _buildFileSectionIndex = 0; +const int _frameworksBuildPhaseSectionIndex = 1; +const int _nativeTargetSectionIndex = 2; +const int _projectSectionIndex = 3; +const int _localSwiftPackageReferenceSectionIndex = 4; +const int _swiftPackageProductDependencySectionIndex = 5; + +List _allSectionsMigrated(SupportedPlatform platform) { + return [ + migratedBuildFileSection, + migratedFrameworksBuildPhaseSection(platform), + migratedNativeTargetSection(platform), + migratedProjectSection(platform), + migratedLocalSwiftPackageReferenceSection(), + migratedSwiftPackageProductDependencySection(), + ]; +} + +List _allSectionsMigratedAsJson(SupportedPlatform platform) { + return [ + migratedBuildFileSectionAsJson, + migratedFrameworksBuildPhaseSectionAsJson(platform), + migratedNativeTargetSectionAsJson(platform), + migratedProjectSectionAsJson(platform), + migratedLocalSwiftPackageReferenceSectionAsJson, + migratedSwiftPackageProductDependencySectionAsJson, + ]; +} + +List _allSectionsUnmigrated(SupportedPlatform platform) { + return [ + unmigratedBuildFileSection, + unmigratedFrameworksBuildPhaseSection(platform), + unmigratedNativeTargetSection(platform), + unmigratedProjectSection(platform), + unmigratedLocalSwiftPackageReferenceSection(), + unmigratedSwiftPackageProductDependencySection(), + ]; +} + +List _allSectionsUnmigratedAsJson(SupportedPlatform platform) { + return [ + unmigratedBuildFileSectionAsJson, + unmigratedFrameworksBuildPhaseSectionAsJson(platform), + unmigratedNativeTargetSectionAsJson(platform), + unmigratedProjectSectionAsJson(platform), + ]; +} + +String _plutilOutput(List objects) { + return ''' +{ + "archiveVersion" : "1", + "classes" : { + + }, + "objects" : { +${objects.join(',\n')} + } +} +'''; +} + +String _projectSettings(List objects) { + return ''' +${objects.join('\n')} +'''; +} + +String _runnerFrameworksBuildPhaseIdentifer(SupportedPlatform platform) { + return platform == SupportedPlatform.ios + ? '97C146EB1CF9000F007C117D' + : '33CC10EA2044A3C60003C045'; +} + +String _runnerNativeTargetIdentifer(SupportedPlatform platform) { + return platform == SupportedPlatform.ios + ? '97C146ED1CF9000F007C117D' + : '33CC10EC2044A3C60003C045'; +} + +String _projectIdentifier(SupportedPlatform platform) { + return platform == SupportedPlatform.ios + ? '97C146E61CF9000F007C117D' + : '33CC10E52044A3C60003C045'; +} + +// PBXBuildFile +const String unmigratedBuildFileSection = ''' +/* Begin PBXBuildFile section */ + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; +/* End PBXBuildFile section */ +'''; +const String migratedBuildFileSection = ''' +/* Begin PBXBuildFile section */ + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ +'''; +const String unmigratedBuildFileSectionAsJson = ''' + "97C146FC1CF9000F007C117D" : { + "fileRef" : "97C146FA1CF9000F007C117D", + "isa" : "PBXBuildFile" + }, + "74858FAF1ED2DC5600515810" : { + "fileRef" : "74858FAE1ED2DC5600515810", + "isa" : "PBXBuildFile" + }'''; +const String migratedBuildFileSectionAsJson = ''' + "78A318202AECB46A00862997" : { + "isa" : "PBXBuildFile", + "productRef" : "78A3181F2AECB46A00862997" + }, + "97C146FC1CF9000F007C117D" : { + "fileRef" : "97C146FA1CF9000F007C117D", + "isa" : "PBXBuildFile" + }, + "74858FAF1ED2DC5600515810" : { + "fileRef" : "74858FAE1ED2DC5600515810", + "isa" : "PBXBuildFile" + }'''; + +// PBXFrameworksBuildPhase +String unmigratedFrameworksBuildPhaseSection( + SupportedPlatform platform, { + bool withCocoapods = false, + bool missingFiles = false, +}) { + return [ + '/* Begin PBXFrameworksBuildPhase section */', + ' ${_runnerFrameworksBuildPhaseIdentifer(platform)} /* Frameworks */ = {', + ' isa = PBXFrameworksBuildPhase;', + ' buildActionMask = 2147483647;', + if (!missingFiles) ...[ + ' files = (', + if (withCocoapods) + ' FD5BB45FB410D26C457F3823 /* Pods_Runner.framework in Frameworks */,', + ' );', + ], + ' runOnlyForDeploymentPostprocessing = 0;', + ' };', + '/* End PBXFrameworksBuildPhase section */', + ].join('\n'); +} + +String migratedFrameworksBuildPhaseSection( + SupportedPlatform platform, { + bool withCocoapods = false, + bool missingFiles = false, +}) { + final List filesField = [ + ' files = (', + ' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,', + if (withCocoapods) + ' FD5BB45FB410D26C457F3823 /* Pods_Runner.framework in Frameworks */,', + ' );', + ]; + return [ + '/* Begin PBXFrameworksBuildPhase section */', + ' ${_runnerFrameworksBuildPhaseIdentifer(platform)} /* Frameworks */ = {', + if (missingFiles) ...filesField, + ' isa = PBXFrameworksBuildPhase;', + ' buildActionMask = 2147483647;', + if (!missingFiles) ...filesField, + ' runOnlyForDeploymentPostprocessing = 0;', + ' };', + '/* End PBXFrameworksBuildPhase section */', + ].join('\n'); +} + +String unmigratedFrameworksBuildPhaseSectionAsJson( + SupportedPlatform platform, { + bool withCocoapods = false, + bool missingFiles = false, +}) { + return [ + ' "${_runnerFrameworksBuildPhaseIdentifer(platform)}" : {', + ' "buildActionMask" : "2147483647",', + if (!missingFiles) ...[ + ' "files" : [', + if (withCocoapods) ' "FD5BB45FB410D26C457F3823"', + ' ],', + ], + ' "isa" : "PBXFrameworksBuildPhase",', + ' "runOnlyForDeploymentPostprocessing" : "0"', + ' }', + ].join('\n'); +} + +String migratedFrameworksBuildPhaseSectionAsJson(SupportedPlatform platform) { + return ''' + "${_runnerFrameworksBuildPhaseIdentifer(platform)}" : { + "buildActionMask" : "2147483647", + "files" : [ + "78A318202AECB46A00862997" + ], + "isa" : "PBXFrameworksBuildPhase", + "runOnlyForDeploymentPostprocessing" : "0" + }'''; +} + +// PBXNativeTarget +String unmigratedNativeTargetSection( + SupportedPlatform platform, { + bool missingPackageProductDependencies = false, + bool withOtherDependency = false, +}) { + return [ + '/* Begin PBXNativeTarget section */', + ' ${_runnerNativeTargetIdentifer(platform)} /* Runner */ = {', + ' isa = PBXNativeTarget;', + ' buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;', + ' buildPhases = (', + ' 9740EEB61CF901F6004384FC /* Run Script */,', + ' 97C146EA1CF9000F007C117D /* Sources */,', + ' ${_runnerFrameworksBuildPhaseIdentifer(platform)} /* Frameworks */,', + ' 97C146EC1CF9000F007C117D /* Resources */,', + ' 9705A1C41CF9048500538489 /* Embed Frameworks */,', + ' 3B06AD1E1E4923F5004D2608 /* Thin Binary */,', + ' );', + ' buildRules = (', + ' );', + ' dependencies = (', + ' );', + ' name = Runner;', + if (!missingPackageProductDependencies) ...[ + ' packageProductDependencies = (', + if (withOtherDependency) + ' 010101010101010101010101 /* SomeOtherPackage */,', + ' );', + ], + ' productName = Runner;', + ' productReference = 97C146EE1CF9000F007C117D /* Runner.app */;', + ' productType = "com.apple.product-type.application";', + ' };', + '/* End PBXNativeTarget section */', + ].join('\n'); +} + +String migratedNativeTargetSection( + SupportedPlatform platform, { + bool missingPackageProductDependencies = false, + bool withOtherDependency = false, +}) { + final List packageDependencies = [ + ' packageProductDependencies = (', + ' 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,', + if (withOtherDependency) + ' 010101010101010101010101 /* SomeOtherPackage */,', + ' );', + ]; + return [ + '/* Begin PBXNativeTarget section */', + ' ${_runnerNativeTargetIdentifer(platform)} /* Runner */ = {', + if (missingPackageProductDependencies) ...packageDependencies, + ' isa = PBXNativeTarget;', + ' buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;', + ' buildPhases = (', + ' 9740EEB61CF901F6004384FC /* Run Script */,', + ' 97C146EA1CF9000F007C117D /* Sources */,', + ' ${_runnerFrameworksBuildPhaseIdentifer(platform)} /* Frameworks */,', + ' 97C146EC1CF9000F007C117D /* Resources */,', + ' 9705A1C41CF9048500538489 /* Embed Frameworks */,', + ' 3B06AD1E1E4923F5004D2608 /* Thin Binary */,', + ' );', + ' buildRules = (', + ' );', + ' dependencies = (', + ' );', + ' name = Runner;', + if (!missingPackageProductDependencies) ...packageDependencies, + ' productName = Runner;', + ' productReference = 97C146EE1CF9000F007C117D /* Runner.app */;', + ' productType = "com.apple.product-type.application";', + ' };', + '/* End PBXNativeTarget section */', + ].join('\n'); +} + +String unmigratedNativeTargetSectionAsJson( + SupportedPlatform platform, { + bool missingPackageProductDependencies = false, +}) { + return [ + ' "${_runnerNativeTargetIdentifer(platform)}" : {', + ' "buildConfigurationList" : "97C147051CF9000F007C117D",', + ' "buildPhases" : [', + ' "9740EEB61CF901F6004384FC",', + ' "97C146EA1CF9000F007C117D",', + ' "${_runnerFrameworksBuildPhaseIdentifer(platform)}",', + ' "97C146EC1CF9000F007C117D",', + ' "9705A1C41CF9048500538489",', + ' "3B06AD1E1E4923F5004D2608"', + ' ],', + ' "buildRules" : [', + ' ],', + ' "dependencies" : [', + ' ],', + ' "isa" : "PBXNativeTarget",', + ' "name" : "Runner",', + if (!missingPackageProductDependencies) ...[ + ' "packageProductDependencies" : [', + ' ],', + ], + ' "productName" : "Runner",', + ' "productReference" : "97C146EE1CF9000F007C117D",', + ' "productType" : "com.apple.product-type.application"', + ' }', + ].join('\n'); +} + +String migratedNativeTargetSectionAsJson(SupportedPlatform platform) { + return ''' + "${_runnerNativeTargetIdentifer(platform)}" : { + "buildConfigurationList" : "97C147051CF9000F007C117D", + "buildPhases" : [ + "9740EEB61CF901F6004384FC", + "97C146EA1CF9000F007C117D", + "${_runnerFrameworksBuildPhaseIdentifer(platform)}", + "97C146EC1CF9000F007C117D", + "9705A1C41CF9048500538489", + "3B06AD1E1E4923F5004D2608" + ], + "buildRules" : [ + + ], + "dependencies" : [ + + ], + "isa" : "PBXNativeTarget", + "name" : "Runner", + "packageProductDependencies" : [ + "78A3181F2AECB46A00862997" + ], + "productName" : "Runner", + "productReference" : "97C146EE1CF9000F007C117D", + "productType" : "com.apple.product-type.application" + }'''; +} + +// PBXProject +String unmigratedProjectSection( + SupportedPlatform platform, { + bool missingPackageReferences = false, + bool withOtherReference = false, +}) { + return [ + '/* Begin PBXProject section */', + ' ${_projectIdentifier(platform)} /* Project object */ = {', + ' isa = PBXProject;', + ' attributes = {', + ' BuildIndependentTargetsInParallel = YES;', + ' LastUpgradeCheck = 1510;', + ' ORGANIZATIONNAME = "";', + ' TargetAttributes = {', + ' 331C8080294A63A400263BE5 = {', + ' CreatedOnToolsVersion = 14.0;', + ' TestTargetID = ${_runnerNativeTargetIdentifer(platform)};', + ' };', + ' ${_runnerNativeTargetIdentifer(platform)} = {', + ' CreatedOnToolsVersion = 7.3.1;', + ' LastSwiftMigration = 1100;', + ' };', + ' };', + ' };', + ' buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;', + ' compatibilityVersion = "Xcode 9.3";', + ' developmentRegion = en;', + ' hasScannedForEncodings = 0;', + ' knownRegions = (', + ' en,', + ' Base,', + ' );', + ' mainGroup = 97C146E51CF9000F007C117D;', + if (!missingPackageReferences) ...[ + ' packageReferences = (', + if (withOtherReference) + ' 010101010101010101010101 /* XCLocalSwiftPackageReference "SomeOtherPackage" */,', + ' );', + ], + ' productRefGroup = 97C146EF1CF9000F007C117D /* Products */;', + ' projectDirPath = "";', + ' projectRoot = "";', + ' targets = (', + ' ${_runnerNativeTargetIdentifer(platform)} /* Runner */,', + ' 331C8080294A63A400263BE5 /* RunnerTests */,', + ' );', + ' };', + '/* End PBXProject section */', + ].join('\n'); +} + +String migratedProjectSection( + SupportedPlatform platform, { + bool missingPackageReferences = false, + bool withOtherReference = false, +}) { + final List packageDependencies = [ + ' packageReferences = (', + ' 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,', + if (withOtherReference) + ' 010101010101010101010101 /* XCLocalSwiftPackageReference "SomeOtherPackage" */,', + ' );', + ]; + return [ + '/* Begin PBXProject section */', + ' ${_projectIdentifier(platform)} /* Project object */ = {', + if (missingPackageReferences) ...packageDependencies, + ' isa = PBXProject;', + ' attributes = {', + ' BuildIndependentTargetsInParallel = YES;', + ' LastUpgradeCheck = 1510;', + ' ORGANIZATIONNAME = "";', + ' TargetAttributes = {', + ' 331C8080294A63A400263BE5 = {', + ' CreatedOnToolsVersion = 14.0;', + ' TestTargetID = ${_runnerNativeTargetIdentifer(platform)};', + ' };', + ' ${_runnerNativeTargetIdentifer(platform)} = {', + ' CreatedOnToolsVersion = 7.3.1;', + ' LastSwiftMigration = 1100;', + ' };', + ' };', + ' };', + ' buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;', + ' compatibilityVersion = "Xcode 9.3";', + ' developmentRegion = en;', + ' hasScannedForEncodings = 0;', + ' knownRegions = (', + ' en,', + ' Base,', + ' );', + ' mainGroup = 97C146E51CF9000F007C117D;', + if (!missingPackageReferences) ...packageDependencies, + ' productRefGroup = 97C146EF1CF9000F007C117D /* Products */;', + ' projectDirPath = "";', + ' projectRoot = "";', + ' targets = (', + ' ${_runnerNativeTargetIdentifer(platform)} /* Runner */,', + ' 331C8080294A63A400263BE5 /* RunnerTests */,', + ' );', + ' };', + '/* End PBXProject section */', + ].join('\n'); +} + +String unmigratedProjectSectionAsJson( + SupportedPlatform platform, { + bool missingPackageReferences = false, +}) { + return [ + ' "${_projectIdentifier(platform)}" : {', + ' "attributes" : {', + ' "BuildIndependentTargetsInParallel" : "YES",', + ' "LastUpgradeCheck" : "1510",', + ' "ORGANIZATIONNAME" : "",', + ' "TargetAttributes" : {', + ' "${_runnerNativeTargetIdentifer(platform)}" : {', + ' "CreatedOnToolsVersion" : "7.3.1",', + ' "LastSwiftMigration" : "1100"', + ' },', + ' "331C8080294A63A400263BE5" : {', + ' "CreatedOnToolsVersion" : "14.0",', + ' "TestTargetID" : "${_runnerNativeTargetIdentifer(platform)}"', + ' }', + ' }', + ' },', + ' "buildConfigurationList" : "97C146E91CF9000F007C117D",', + ' "compatibilityVersion" : "Xcode 9.3",', + ' "developmentRegion" : "en",', + ' "hasScannedForEncodings" : "0",', + ' "isa" : "PBXProject",', + ' "knownRegions" : [', + ' "en",', + ' "Base"', + ' ],', + ' "mainGroup" : "97C146E51CF9000F007C117D",', + if (!missingPackageReferences) ...[ + ' "packageReferences" : [', + ' ],', + ], + ' "productRefGroup" : "97C146EF1CF9000F007C117D",', + ' "projectDirPath" : "",', + ' "projectRoot" : "",', + ' "targets" : [', + ' "${_runnerNativeTargetIdentifer(platform)}",', + ' "331C8080294A63A400263BE5"', + ' ]', + ' }', + ].join('\n'); +} + +String migratedProjectSectionAsJson(SupportedPlatform platform) { + return ''' + "${_projectIdentifier(platform)}" : { + "attributes" : { + "BuildIndependentTargetsInParallel" : "YES", + "LastUpgradeCheck" : "1510", + "ORGANIZATIONNAME" : "", + "TargetAttributes" : { + "${_runnerNativeTargetIdentifer(platform)}" : { + "CreatedOnToolsVersion" : "7.3.1", + "LastSwiftMigration" : "1100" + }, + "331C8080294A63A400263BE5" : { + "CreatedOnToolsVersion" : "14.0", + "TestTargetID" : "${_runnerNativeTargetIdentifer(platform)}" + } + } + }, + "buildConfigurationList" : "97C146E91CF9000F007C117D", + "compatibilityVersion" : "Xcode 9.3", + "developmentRegion" : "en", + "hasScannedForEncodings" : "0", + "isa" : "PBXProject", + "knownRegions" : [ + "en", + "Base" + ], + "mainGroup" : "97C146E51CF9000F007C117D", + "packageReferences" : [ + "781AD8BC2B33823900A9FFBB" + ], + "productRefGroup" : "97C146EF1CF9000F007C117D", + "projectDirPath" : "", + "projectRoot" : "", + "targets" : [ + "${_runnerNativeTargetIdentifer(platform)}", + "331C8080294A63A400263BE5" + ] + }'''; +} + +// XCLocalSwiftPackageReference +String unmigratedLocalSwiftPackageReferenceSection({ + bool withOtherReference = false, +}) { + return [ + '/* Begin XCLocalSwiftPackageReference section */', + if (withOtherReference) ...[ + ' 010101010101010101010101 /* XCLocalSwiftPackageReference "SomeOtherPackage" */ = {', + ' isa = XCLocalSwiftPackageReference;', + ' relativePath = SomeOtherPackage;', + ' };', + ], + '/* End XCLocalSwiftPackageReference section */', + ].join('\n'); +} + +String migratedLocalSwiftPackageReferenceSection({ + bool withOtherReference = false, +}) { + return [ + '/* Begin XCLocalSwiftPackageReference section */', + if (withOtherReference) ...[ + ' 010101010101010101010101 /* XCLocalSwiftPackageReference "SomeOtherPackage" */ = {', + ' isa = XCLocalSwiftPackageReference;', + ' relativePath = SomeOtherPackage;', + ' };', + ], + ' 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {', + ' isa = XCLocalSwiftPackageReference;', + ' relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;', + ' };', + '/* End XCLocalSwiftPackageReference section */', + ].join('\n'); +} + +const String migratedLocalSwiftPackageReferenceSectionAsJson = ''' + "781AD8BC2B33823900A9FFBB" : { + "isa" : "XCLocalSwiftPackageReference", + "relativePath" : "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" + }'''; + +// XCSwiftPackageProductDependency +String unmigratedSwiftPackageProductDependencySection({ + bool withOtherDependency = false, +}) { + return [ + '/* Begin XCSwiftPackageProductDependency section */', + if (withOtherDependency) ...[ + ' 010101010101010101010101 /* SomeOtherPackage */ = {', + ' isa = XCSwiftPackageProductDependency;', + ' productName = SomeOtherPackage;', + ' };', + ], + '/* End XCSwiftPackageProductDependency section */', + ].join('\n'); +} + +String migratedSwiftPackageProductDependencySection({ + bool withOtherDependency = false, +}) { + return [ + '/* Begin XCSwiftPackageProductDependency section */', + if (withOtherDependency) ...[ + ' 010101010101010101010101 /* SomeOtherPackage */ = {', + ' isa = XCSwiftPackageProductDependency;', + ' productName = SomeOtherPackage;', + ' };', + ], + ' 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {', + ' isa = XCSwiftPackageProductDependency;', + ' productName = FlutterGeneratedPluginSwiftPackage;', + ' };', + '/* End XCSwiftPackageProductDependency section */', + ].join('\n'); +} + +const String migratedSwiftPackageProductDependencySectionAsJson = ''' + "78A3181F2AECB46A00862997" : { + "isa" : "XCSwiftPackageProductDependency", + "productName" : "FlutterGeneratedPluginSwiftPackage" + }'''; + +class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { + FakeXcodeProjectInterpreter({ + this.throwErrorOnGetInfo = false, + }); + + @override + bool isInstalled = false; + + @override + List xcrunCommand() => ['xcrun']; + + final bool throwErrorOnGetInfo; + + @override + Future getInfo(String projectPath, {String? projectFilename}) async { + if (throwErrorOnGetInfo) { + throwToolExit('Unable to get Xcode project information'); + } + return null; + } +} + +class FakePlistParser extends Fake implements PlistParser { + FakePlistParser({ + String? json, + }) : _outputPerCall = (json != null) ? [json] : null; + + FakePlistParser.multiple(this._outputPerCall); + + final List? _outputPerCall; + + @override + String? plistJsonContent(String filePath) { + if (_outputPerCall != null && _outputPerCall.isNotEmpty) { + return _outputPerCall.removeAt(0); + } + return null; + } + + bool get hasRemainingExpectations { + return _outputPerCall != null && _outputPerCall.isNotEmpty; + } +} + +class FakeXcodeProject extends Fake implements IosProject { + FakeXcodeProject({ + required MemoryFileSystem fileSystem, + required String platform, + required this.logger, + }) : hostAppRoot = fileSystem.directory('app_name').childDirectory(platform), + parent = FakeFlutterProject(fileSystem: fileSystem); + + final Logger logger; + late XcodeProjectInfo? _projectInfo = XcodeProjectInfo( + ['Runner'], + ['Debug', 'Release', 'Profile'], + ['Runner'], + logger, + ); + + @override + Directory hostAppRoot; + + @override + FakeFlutterProject parent; + + @override + Directory get xcodeProject => hostAppRoot.childDirectory('$hostAppProjectName.xcodeproj'); + + @override + File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj'); + + @override + late Directory? xcodeWorkspace = hostAppRoot.childDirectory('$hostAppProjectName.xcworkspace'); + + @override + String hostAppProjectName = 'Runner'; + + @override + Directory get flutterPluginSwiftPackageDirectory => hostAppRoot + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage'); + + @override + File get flutterPluginSwiftPackageManifest => + flutterPluginSwiftPackageDirectory.childFile('Package.swift'); + + @override + bool get flutterPluginSwiftPackageInProjectSettings { + return xcodeProjectInfoFile.existsSync() && + xcodeProjectInfoFile + .readAsStringSync() + .contains('FlutterGeneratedPluginSwiftPackage'); + } + + @override + Future projectInfo() async { + return _projectInfo; + } + + @override + File xcodeProjectSchemeFile({String? scheme}) { + final String schemeName = scheme ?? 'Runner'; + return xcodeProject.childDirectory('xcshareddata').childDirectory('xcschemes').childFile('$schemeName.xcscheme'); + } +} + +class FakeFlutterProject extends Fake implements FlutterProject { + FakeFlutterProject({ + required MemoryFileSystem fileSystem, + }) : directory = fileSystem.directory('app_name'); + + @override + Directory directory; +} + +class FakeSwiftPackageManagerIntegrationMigration extends SwiftPackageManagerIntegrationMigration { + FakeSwiftPackageManagerIntegrationMigration( + super.project, + super.platform, + super.buildInfo, { + required super.xcodeProjectInterpreter, + required super.logger, + required super.fileSystem, + required super.plistParser, + this.validateBackup = false, + }) : _xcodeProject = project; + + final XcodeBasedProject _xcodeProject; + + final bool validateBackup; + @override + void restoreFromBackup(SchemeInfo? schemeInfo) { + if (validateBackup) { + expect(backupProjectSettings.existsSync(), isTrue); + final String originalSettings = backupProjectSettings.readAsStringSync(); + expect( + _xcodeProject.xcodeProjectInfoFile.readAsStringSync() == originalSettings, + isFalse, + ); + + expect(schemeInfo?.backupSchemeFile, isNotNull); + final File backupScheme = schemeInfo!.backupSchemeFile!; + expect(backupScheme.existsSync(), isTrue); + final String originalScheme = backupScheme.readAsStringSync(); + expect( + _xcodeProject.xcodeProjectSchemeFile().readAsStringSync() == originalScheme, + isFalse, + ); + + super.restoreFromBackup(schemeInfo); + expect( + _xcodeProject.xcodeProjectInfoFile.readAsStringSync(), + originalSettings, + ); + expect( + _xcodeProject.xcodeProjectSchemeFile().readAsStringSync(), + originalScheme, + ); + } else { + super.restoreFromBackup(schemeInfo); + } + } +} diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 2084c4227a84..802d382ef6dc 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -16,6 +16,8 @@ import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_plugins.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/macos/darwin_dependency_management.dart'; +import 'package:flutter_tools/src/platform_plugins.dart'; import 'package:flutter_tools/src/plugins.dart'; import 'package:flutter_tools/src/preview_device.dart'; import 'package:flutter_tools/src/project.dart'; @@ -522,6 +524,7 @@ dependencies: expect(jsonContent['dependencyGraph'], expectedDependencyGraph); expect(jsonContent['date_created'], dateCreated.toString()); expect(jsonContent['version'], '1.0.0'); + expect(jsonContent['swift_package_manager_enabled'], false); // Make sure tests are updated if a new object is added/removed. final List expectedKeys = [ @@ -530,6 +533,7 @@ dependencies: 'dependencyGraph', 'date_created', 'version', + 'swift_package_manager_enabled', ]; expect(jsonContent.keys, expectedKeys); }, overrides: { @@ -609,6 +613,79 @@ dependencies: FlutterVersion: () => flutterVersion, }); + testUsingContext( + '.flutter-plugins-dependencies contains swift_package_manager_enabled true when project is using Swift Package Manager', () async { + createPlugin( + name: 'plugin-a', + platforms: const { + // Native-only; should include native build. + 'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'), + // Hybrid native and Dart; should include native build. + 'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar', sharedDarwinSource: true), + // Web; should not have the native build key at all since it doesn't apply. + 'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'), + // Dart-only; should not include native build. + 'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'), + }); + iosProject.testExists = true; + + final DateTime dateCreated = DateTime(1970); + systemClock.currentTime = dateCreated; + + flutterProject.usesSwiftPackageManager = true; + + await refreshPluginsList(flutterProject); + + expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true); + final String pluginsString = flutterProject.flutterPluginsDependenciesFile + .readAsStringSync(); + final Map jsonContent = json.decode(pluginsString) as Map; + + expect(jsonContent['swift_package_manager_enabled'], true); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + SystemClock: () => systemClock, + FlutterVersion: () => flutterVersion, + }); + + testUsingContext( + '.flutter-plugins-dependencies contains swift_package_manager_enabled false when project is using Swift Package Manager but forceCocoaPodsOnly is true', + () async { + createPlugin( + name: 'plugin-a', + platforms: const { + // Native-only; should include native build. + 'android': _PluginPlatformInfo(pluginClass: 'Foo', androidPackage: 'bar.foo'), + // Hybrid native and Dart; should include native build. + 'ios': _PluginPlatformInfo(pluginClass: 'Foo', dartPluginClass: 'Bar', sharedDarwinSource: true), + // Web; should not have the native build key at all since it doesn't apply. + 'web': _PluginPlatformInfo(pluginClass: 'Foo', fileName: 'lib/foo.dart'), + // Dart-only; should not include native build. + 'windows': _PluginPlatformInfo(dartPluginClass: 'Foo'), + }); + iosProject.testExists = true; + + final DateTime dateCreated = DateTime(1970); + systemClock.currentTime = dateCreated; + + flutterProject.usesSwiftPackageManager = true; + + await refreshPluginsList(flutterProject, forceCocoaPodsOnly: true); + + expect(flutterProject.flutterPluginsDependenciesFile.existsSync(), true); + final String pluginsString = flutterProject.flutterPluginsDependenciesFile + .readAsStringSync(); + final Map jsonContent = json.decode(pluginsString) as Map; + + expect(jsonContent['swift_package_manager_enabled'], false); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + SystemClock: () => systemClock, + FlutterVersion: () => flutterVersion, + }); + testUsingContext('Changes to the plugin list invalidates the Cocoapod lockfiles', () async { simulatePodInstallRun(iosProject); simulatePodInstallRun(macosProject); @@ -886,8 +963,12 @@ flutter: ios: dartPluginClass: SomePlugin '''); - - await injectPlugins(flutterProject, iosPlatform: true); + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + iosPlatform: true, + darwinDependencyManagement: dependencyManagement, + ); final File registrantFile = iosProject.pluginRegistrantImplementation; @@ -909,8 +990,12 @@ flutter: macos: dartPluginClass: SomePlugin '''); - - await injectPlugins(flutterProject, macOSPlatform: true); + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + macOSPlatform: true, + darwinDependencyManagement: dependencyManagement, + ); final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift'); @@ -933,8 +1018,12 @@ flutter: pluginClass: none dartPluginClass: SomePlugin '''); - - await injectPlugins(flutterProject, macOSPlatform: true); + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + macOSPlatform: true, + darwinDependencyManagement: dependencyManagement, + ); final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift'); @@ -953,8 +1042,12 @@ flutter: pluginDirectory.childFile('pubspec.yaml').writeAsStringSync(r''' "aws ... \"Branch\": $BITBUCKET_BRANCH, \"Date\": $(date +"%m-%d-%y"), \"Time\": $(date +"%T")}\" '''); - - await injectPlugins(flutterProject, macOSPlatform: true); + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + macOSPlatform: true, + darwinDependencyManagement: dependencyManagement, + ); final File registrantFile = macosProject.managedDirectory.childFile('GeneratedPluginRegistrant.swift'); @@ -1194,6 +1287,35 @@ The Flutter Preview device does not support the following plugins from your pubs FileSystem: () => fsWindows, ProcessManager: () => FakeProcessManager.empty(), }); + + testUsingContext('iOS and macOS project setup up Darwin Dependency Management', () async { + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + iosPlatform: true, + macOSPlatform: true, + darwinDependencyManagement: dependencyManagement, + ); + expect( + dependencyManagement.setupPlatforms, + [SupportedPlatform.ios, SupportedPlatform.macos], + ); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('non-iOS or macOS project does not setup up Darwin Dependency Management', () async { + final FakeDarwinDependencyManagement dependencyManagement = FakeDarwinDependencyManagement(); + await injectPlugins( + flutterProject, + darwinDependencyManagement: dependencyManagement, + ); + expect(dependencyManagement.setupPlatforms, []); + }, overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }); }); group('createPluginSymlinks', () { @@ -1390,6 +1512,154 @@ The Flutter Preview device does not support the following plugins from your pubs }); + group('Plugin files', () { + testWithoutContext('pluginSwiftPackageManifestPath for iOS and macOS plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: ''), + MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test'), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect( + plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey), + '/path/to/test/ios/test/Package.swift', + ); + expect( + plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey), + '/path/to/test/macos/test/Package.swift', + ); + }); + + testWithoutContext('pluginSwiftPackageManifestPath for darwin plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: '', sharedDarwinSource: true), + MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test', sharedDarwinSource: true), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect( + plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey), + '/path/to/test/darwin/test/Package.swift', + ); + expect( + plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey), + '/path/to/test/darwin/test/Package.swift', + ); + }); + + testWithoutContext('pluginSwiftPackageManifestPath for non darwin plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: ''), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect( + plugin.pluginSwiftPackageManifestPath(fs, IOSPlugin.kConfigKey), + isNull, + ); + expect( + plugin.pluginSwiftPackageManifestPath(fs, MacOSPlugin.kConfigKey), + isNull, + ); + expect( + plugin.pluginSwiftPackageManifestPath(fs, WindowsPlugin.kConfigKey), + isNull, + ); + }); + + testWithoutContext('pluginPodspecPath for iOS and macOS plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: ''), + MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test'), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect( + plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey), + '/path/to/test/ios/test.podspec', + ); + expect( + plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey), + '/path/to/test/macos/test.podspec', + ); + }); + + testWithoutContext('pluginPodspecPath for darwin plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + IOSPlugin.kConfigKey: IOSPlugin(name: 'test', classPrefix: '', sharedDarwinSource: true), + MacOSPlugin.kConfigKey: MacOSPlugin(name: 'test', sharedDarwinSource: true), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect( + plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey), + '/path/to/test/darwin/test.podspec', + ); + expect( + plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey), + '/path/to/test/darwin/test.podspec', + ); + }); + + testWithoutContext('pluginPodspecPath for non darwin plugins', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Plugin plugin = Plugin( + name: 'test', + path: '/path/to/test/', + defaultPackagePlatforms: const {}, + pluginDartClassPlatforms: const {}, + platforms: const { + WindowsPlugin.kConfigKey: WindowsPlugin(name: 'test', pluginClass: ''), + }, + dependencies: [], + isDirectDependency: true, + ); + + expect(plugin.pluginPodspecPath(fs, IOSPlugin.kConfigKey), isNull); + expect(plugin.pluginPodspecPath(fs, MacOSPlugin.kConfigKey), isNull); + expect(plugin.pluginPodspecPath(fs, WindowsPlugin.kConfigKey), isNull); + }); + }); testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async { final Platform platform = FakePlatform(operatingSystem: 'windows'); final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]'); @@ -1498,6 +1768,9 @@ class FakeFlutterProject extends Fake implements FlutterProject { @override bool isModule = false; + @override + bool usesSwiftPackageManager = false; + @override late FlutterManifest manifest; @@ -1684,3 +1957,14 @@ class FakeSystemClock extends Fake implements SystemClock { return currentTime; } } + +class FakeDarwinDependencyManagement extends Fake implements DarwinDependencyManagement { + List setupPlatforms = []; + + @override + Future setUp({ + required SupportedPlatform platform, + }) async { + setupPlatforms.add(platform); + } +} diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 2934a2d5f8fd..c6e4e52678e7 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -375,6 +375,91 @@ void main() { }); }); + group('usesSwiftPackageManager', () { + testUsingContext('is true when iOS project exists', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('ios').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isTrue); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + + testUsingContext('is true when macOS project exists', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('macos').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isTrue); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + + testUsingContext('is false when disabled via manifest', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('ios').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(disabledSwiftPackageManager: true); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isFalse); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + + testUsingContext("is false when iOS and macOS project don't exist", () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + final FlutterManifest manifest = FakeFlutterManifest(); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isFalse); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + + testUsingContext('is false when Xcode is less than 15', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('ios').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isFalse); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(14, 0, 0)), + }); + + testUsingContext('is false when Swift Package Manager feature is not enabled', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('ios').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isFalse); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + + testUsingContext('is false when project is a module', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory projectDirectory = fs.directory('path'); + projectDirectory.childDirectory('ios').createSync(recursive: true); + final FlutterManifest manifest = FakeFlutterManifest(isModule: true); + final FlutterProject project = FlutterProject(projectDirectory, manifest, manifest); + expect(project.usesSwiftPackageManager, isFalse); + }, overrides: { + FeatureFlags: () => TestFeatureFlags(isSwiftPackageManagerEnabled: true), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(version: Version(15, 0, 0)), + }); + }); + group('java gradle agp compatibility', () { Future configureGradleAgpForTest({ required String gradleV, @@ -1046,6 +1131,31 @@ plugins { expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject'); }); }); + + group('flutterSwiftPackageInProjectSettings', () { + testWithMocks('is false if pbxproj missing', () async { + final FlutterProject project = await someProject(); + expect(project.ios.xcodeProjectInfoFile.existsSync(), isFalse); + expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse); + }); + + testWithMocks('is false if pbxproj does not contain FlutterGeneratedPluginSwiftPackage in build process', () async { + final FlutterProject project = await someProject(); + project.ios.xcodeProjectInfoFile.createSync(recursive: true); + expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue); + expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isFalse); + }); + + testWithMocks('is true if pbxproj does contain FlutterGeneratedPluginSwiftPackage in build process', () async { + final FlutterProject project = await someProject(); + project.ios.xcodeProjectInfoFile.createSync(recursive: true); + project.ios.xcodeProjectInfoFile.writeAsStringSync(''' +' 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };'; +'''); + expect(project.ios.xcodeProjectInfoFile.existsSync(), isTrue); + expect(project.ios.flutterPluginSwiftPackageInProjectSettings, isTrue); + }); + }); }); group('application bundle name', () { @@ -1724,6 +1834,10 @@ File androidPluginRegistrant(Directory parent) { } class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { + FakeXcodeProjectInterpreter({ + this.version, + }); + final Map> buildSettingsByBuildContext = >{}; late XcodeProjectInfo xcodeProjectInfo; @@ -1745,6 +1859,9 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete @override bool get isInstalled => true; + + @override + Version? version; } class FakeAndroidSdkWithDir extends Fake implements AndroidSdk { @@ -1755,3 +1872,16 @@ class FakeAndroidSdkWithDir extends Fake implements AndroidSdk { @override Directory get directory => _directory; } + +class FakeFlutterManifest extends Fake implements FlutterManifest { + FakeFlutterManifest({ + this.disabledSwiftPackageManager = false, + this.isModule = false, + }); + + @override + bool disabledSwiftPackageManager; + + @override + bool isModule; +} diff --git a/packages/flutter_tools/test/general.shard/runner/runner_test.dart b/packages/flutter_tools/test/general.shard/runner/runner_test.dart index 8954cca4c23f..3bb02b8ccfe9 100644 --- a/packages/flutter_tools/test/general.shard/runner/runner_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/runner_test.dart @@ -353,6 +353,7 @@ void main() { group('unified_analytics', () { late FakeAnalytics fakeAnalytics; late MemoryFileSystem fs; + late TestUsage testUsage; setUp(() { fs = MemoryFileSystem.test(); @@ -361,6 +362,7 @@ void main() { fs: fs, fakeFlutterVersion: FakeFlutterVersion(), ); + testUsage = TestUsage(); }); testUsingContext( @@ -387,6 +389,85 @@ void main() { }, ); + testUsingContext( + 'runner sends mismatch event to ga3 if user opted in to ga3 but out of ga4 analytics', + () async { + io.setExitFunctionForTests((int exitCode) {}); + + // Begin by opting out of telemetry for package:unified_analytics + // and leaving legacy analytics opted in + await fakeAnalytics.setTelemetry(false); + expect(fakeAnalytics.telemetryEnabled, false); + expect(testUsage.enabled, true); + + await runner.run( + [], + () => [], + // This flutterVersion disables crash reporting. + flutterVersion: '[user-branch]/', + shutdownHooks: ShutdownHooks(), + ); + + expect( + testUsage.events, + contains(const TestUsageEvent( + 'ga4_and_ga3_status_mismatch', + 'opted_out_of_ga4', + )), + ); + expect(fakeAnalytics.telemetryEnabled, false); + expect(testUsage.enabled, true); + expect(fakeAnalytics.sentEvents, isEmpty); + + }, + overrides: { + Analytics: () => fakeAnalytics, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Usage: () => testUsage, + }, + ); + + testUsingContext( + 'runner does not send mismatch event to ga3 if user opted out of ga3 & ga4 analytics', + () async { + io.setExitFunctionForTests((int exitCode) {}); + + // Begin by opting out of telemetry for package:unified_analytics + // and legacy analytics + await fakeAnalytics.setTelemetry(false); + testUsage.enabled = false; + expect(fakeAnalytics.telemetryEnabled, false); + expect(testUsage.enabled, false); + + await runner.run( + [], + () => [], + // This flutterVersion disables crash reporting. + flutterVersion: '[user-branch]/', + shutdownHooks: ShutdownHooks(), + ); + + expect( + testUsage.events, + isNot(contains(const TestUsageEvent( + 'ga4_and_ga3_status_mismatch', + 'opted_out_of_ga4', + ))), + ); + expect(fakeAnalytics.telemetryEnabled, false); + expect(testUsage.enabled, false); + expect(fakeAnalytics.sentEvents, isEmpty); + + }, + overrides: { + Analytics: () => fakeAnalytics, + FileSystem: () => MemoryFileSystem.test(), + ProcessManager: () => FakeProcessManager.any(), + Usage: () => testUsage, + }, + ); + testUsingContext( 'runner enabling analytics with flag', () async { diff --git a/packages/flutter_tools/test/general.shard/xcode_backend_test.dart b/packages/flutter_tools/test/general.shard/xcode_backend_test.dart index b91968bd9fdf..61722bd8f807 100644 --- a/packages/flutter_tools/test/general.shard/xcode_backend_test.dart +++ b/packages/flutter_tools/test/general.shard/xcode_backend_test.dart @@ -223,6 +223,179 @@ void main() { ); }); }); + + group('prepare', () { + test('exits with useful error message when build mode not set', () { + final Directory buildDir = fileSystem.directory('/path/to/builds') + ..createSync(recursive: true); + final Directory flutterRoot = fileSystem.directory('/path/to/flutter') + ..createSync(recursive: true); + final File pipe = fileSystem.file('/tmp/pipe') + ..createSync(recursive: true); + const String buildMode = 'Debug'; + final TestContext context = TestContext( + ['prepare'], + { + 'ACTION': 'build', + 'BUILT_PRODUCTS_DIR': buildDir.path, + 'FLUTTER_ROOT': flutterRoot.path, + 'INFOPLIST_PATH': 'Info.plist', + }, + commands: [ + FakeCommand( + command: [ + '${flutterRoot.path}/bin/flutter', + 'assemble', + '--no-version-check', + '--output=${buildDir.path}/', + '-dTargetPlatform=ios', + '-dTargetFile=lib/main.dart', + '-dBuildMode=${buildMode.toLowerCase()}', + '-dIosArchs=', + '-dSdkRoot=', + '-dSplitDebugInfo=', + '-dTreeShakeIcons=', + '-dTrackWidgetCreation=', + '-dDartObfuscation=', + '-dAction=build', + '-dFrontendServerStarterPath=', + '--ExtraGenSnapshotOptions=', + '--DartDefines=', + '--ExtraFrontEndOptions=', + 'debug_unpack_ios', + ], + ), + ], + fileSystem: fileSystem, + scriptOutputStreamFile: pipe, + ); + expect( + () => context.run(), + throwsException, + ); + expect( + context.stderr, + contains('ERROR: Unknown FLUTTER_BUILD_MODE: null.\n'), + ); + }); + test('calls flutter assemble', () { + final Directory buildDir = fileSystem.directory('/path/to/builds') + ..createSync(recursive: true); + final Directory flutterRoot = fileSystem.directory('/path/to/flutter') + ..createSync(recursive: true); + final File pipe = fileSystem.file('/tmp/pipe') + ..createSync(recursive: true); + const String buildMode = 'Debug'; + final TestContext context = TestContext( + ['prepare'], + { + 'BUILT_PRODUCTS_DIR': buildDir.path, + 'CONFIGURATION': buildMode, + 'FLUTTER_ROOT': flutterRoot.path, + 'INFOPLIST_PATH': 'Info.plist', + }, + commands: [ + FakeCommand( + command: [ + '${flutterRoot.path}/bin/flutter', + 'assemble', + '--no-version-check', + '--output=${buildDir.path}/', + '-dTargetPlatform=ios', + '-dTargetFile=lib/main.dart', + '-dBuildMode=${buildMode.toLowerCase()}', + '-dIosArchs=', + '-dSdkRoot=', + '-dSplitDebugInfo=', + '-dTreeShakeIcons=', + '-dTrackWidgetCreation=', + '-dDartObfuscation=', + '-dAction=', + '-dFrontendServerStarterPath=', + '--ExtraGenSnapshotOptions=', + '--DartDefines=', + '--ExtraFrontEndOptions=', + 'debug_unpack_ios', + ], + ), + ], + fileSystem: fileSystem, + scriptOutputStreamFile: pipe, + )..run(); + expect(context.stderr, isEmpty); + }); + + test('forwards all env variables to flutter assemble', () { + final Directory buildDir = fileSystem.directory('/path/to/builds') + ..createSync(recursive: true); + final Directory flutterRoot = fileSystem.directory('/path/to/flutter') + ..createSync(recursive: true); + const String archs = 'arm64'; + const String buildMode = 'Release'; + const String dartObfuscation = 'false'; + const String dartDefines = 'flutter.inspector.structuredErrors%3Dtrue'; + const String expandedCodeSignIdentity = 'F1326572E0B71C3C8442805230CB4B33B708A2E2'; + const String extraFrontEndOptions = '--some-option'; + const String extraGenSnapshotOptions = '--obfuscate'; + const String frontendServerStarterPath = '/path/to/frontend_server_starter.dart'; + const String sdkRoot = '/path/to/sdk'; + const String splitDebugInfo = '/path/to/split/debug/info'; + const String trackWidgetCreation = 'true'; + const String treeShake = 'true'; + final TestContext context = TestContext( + ['prepare'], + { + 'ACTION': 'install', + 'ARCHS': archs, + 'BUILT_PRODUCTS_DIR': buildDir.path, + 'CODE_SIGNING_REQUIRED': 'YES', + 'CONFIGURATION': buildMode, + 'DART_DEFINES': dartDefines, + 'DART_OBFUSCATION': dartObfuscation, + 'EXPANDED_CODE_SIGN_IDENTITY': expandedCodeSignIdentity, + 'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions, + 'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions, + 'FLUTTER_ROOT': flutterRoot.path, + 'FRONTEND_SERVER_STARTER_PATH': frontendServerStarterPath, + 'INFOPLIST_PATH': 'Info.plist', + 'SDKROOT': sdkRoot, + 'FLAVOR': 'strawberry', + 'SPLIT_DEBUG_INFO': splitDebugInfo, + 'TRACK_WIDGET_CREATION': trackWidgetCreation, + 'TREE_SHAKE_ICONS': treeShake, + }, + commands: [ + FakeCommand( + command: [ + '${flutterRoot.path}/bin/flutter', + 'assemble', + '--no-version-check', + '--output=${buildDir.path}/', + '-dTargetPlatform=ios', + '-dTargetFile=lib/main.dart', + '-dBuildMode=${buildMode.toLowerCase()}', + '-dFlavor=strawberry', + '-dIosArchs=$archs', + '-dSdkRoot=$sdkRoot', + '-dSplitDebugInfo=$splitDebugInfo', + '-dTreeShakeIcons=$treeShake', + '-dTrackWidgetCreation=$trackWidgetCreation', + '-dDartObfuscation=$dartObfuscation', + '-dAction=install', + '-dFrontendServerStarterPath=$frontendServerStarterPath', + '--ExtraGenSnapshotOptions=$extraGenSnapshotOptions', + '--DartDefines=$dartDefines', + '--ExtraFrontEndOptions=$extraFrontEndOptions', + '-dCodesignIdentity=$expandedCodeSignIdentity', + 'release_unpack_ios', + ], + ), + ], + fileSystem: fileSystem, + )..run(); + expect(context.stderr, isEmpty); + }); + }); } class TestContext extends Context { diff --git a/packages/flutter_tools/test/general.shard/xcode_project_test.dart b/packages/flutter_tools/test/general.shard/xcode_project_test.dart new file mode 100644 index 000000000000..dfaf6096ccc1 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/xcode_project_test.dart @@ -0,0 +1,213 @@ +// 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:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; + + +void main() { + group('IosProject', () { + testWithoutContext('managedDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(project.managedDirectory.path, 'app_name/ios/Flutter'); + }); + + testWithoutContext('module managedDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs, isModule: true), + ); + expect(project.managedDirectory.path, 'app_name/.ios/Flutter'); + }); + + testWithoutContext('ephemeralDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(project.ephemeralDirectory.path, 'app_name/ios/Flutter/ephemeral'); + }); + + testWithoutContext('module ephemeralDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs, isModule: true), + ); + expect(project.ephemeralDirectory.path, 'app_name/.ios/Flutter/ephemeral'); + }); + + testWithoutContext('flutterPluginSwiftPackageDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect( + project.flutterPluginSwiftPackageDirectory.path, + 'app_name/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', + ); + }); + + testWithoutContext('module flutterPluginSwiftPackageDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs, isModule: true), + ); + expect( + project.flutterPluginSwiftPackageDirectory.path, + 'app_name/.ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', + ); + }); + + testWithoutContext('xcodeConfigFor', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect( + project.xcodeConfigFor('Debug').path, + 'app_name/ios/Flutter/Debug.xcconfig', + ); + }); + + group('projectInfo', () { + testUsingContext('is null if XcodeProjectInterpreter is null', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + project.xcodeProject.createSync(recursive: true); + expect(await project.projectInfo(), isNull); + }, overrides: { + XcodeProjectInterpreter: () => null, + }); + + testUsingContext('is null if XcodeProjectInterpreter is not installed', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + project.xcodeProject.createSync(recursive: true); + expect(await project.projectInfo(), isNull); + }, overrides: { + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter( + isInstalled: false, + ), + }); + + testUsingContext('is null if xcodeproj does not exist', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(await project.projectInfo(), isNull); + }, overrides: { + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(), + }); + + testUsingContext('returns XcodeProjectInfo', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final IosProject project = IosProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + project.xcodeProject.createSync(recursive: true); + expect(await project.projectInfo(), isNotNull); + }, overrides: { + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(), + }); + }); + }); + + group('MacOSProject', () { + testWithoutContext('managedDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final MacOSProject project = MacOSProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(project.managedDirectory.path, 'app_name/macos/Flutter'); + }); + + testWithoutContext('module managedDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final MacOSProject project = MacOSProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(project.managedDirectory.path, 'app_name/macos/Flutter'); + }); + + testWithoutContext('ephemeralDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final MacOSProject project = MacOSProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect(project.ephemeralDirectory.path, 'app_name/macos/Flutter/ephemeral'); + }); + + testWithoutContext('flutterPluginSwiftPackageDirectory', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final MacOSProject project = MacOSProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect( + project.flutterPluginSwiftPackageDirectory.path, + 'app_name/macos/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', + ); + }); + + testWithoutContext('xcodeConfigFor', () { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final MacOSProject project = MacOSProject.fromFlutter( + FakeFlutterProject(fileSystem: fs), + ); + expect( + project.xcodeConfigFor('Debug').path, + 'app_name/macos/Flutter/Flutter-Debug.xcconfig', + ); + }); + }); +} + +class FakeFlutterProject extends Fake implements FlutterProject { + FakeFlutterProject({ + required this.fileSystem, + this.isModule = false, + }); + + MemoryFileSystem fileSystem; + + @override + late final Directory directory = fileSystem.directory('app_name'); + + @override + bool isModule = false; +} + +class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterpreter { + FakeXcodeProjectInterpreter({ + this.isInstalled = true, + }); + + @override + final bool isInstalled; + + @override + Future getInfo(String projectPath, {String? projectFilename}) async { + return XcodeProjectInfo( + [], + [], + ['Runner'], + BufferLogger.test(), + ); + } +} diff --git a/packages/flutter_tools/test/integration.shard/asset_transformation_test.dart b/packages/flutter_tools/test/integration.shard/asset_transformation_test.dart new file mode 100644 index 000000000000..e53ea82b08db --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/asset_transformation_test.dart @@ -0,0 +1,153 @@ +// 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:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; + +import '../src/common.dart'; +import 'test_data/project.dart'; +import 'test_utils.dart'; + +final class _AssetTransformationTestProject extends Project { + @override + final String pubspec = ''' +name: test +environment: + sdk: '>=3.2.0-0 <4.0.0' +dependencies: + flutter: + sdk: flutter +dev_dependencies: + capitalizer_transformer: + path: ./capitalizer_transformer +flutter: + assets: + - path: assets/text_asset.txt + transformers: + - package: capitalizer_transformer +'''; + + @override + final String main = ''' +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: Text('Hello, World!'), + ), + ), + ); + } +} +'''; +} + +Future main() async { + testWithoutContext('asset is transformed when declared with a transformation', () async { + final Directory tempProjectDirectory = createResolvedTempDirectorySync( + 'asset_transformation_test.', + ); + + try { + _setUpCapitalizerTransformer(tempProjectDirectory); + await _AssetTransformationTestProject().setUpIn(tempProjectDirectory); + tempProjectDirectory.childDirectory('assets').childFile('text_asset.txt') + ..createSync(recursive: true) + ..writeAsStringSync('abc'); + + final String flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + final ProcessResult result = await processManager.run( + [ + flutterBin, + 'build', + 'web', + ], + workingDirectory: tempProjectDirectory.path, + ); + + expect(result.exitCode, 0, reason: result.stderr as String); + + final File asset = fileSystem.file( + fileSystem.path.join( + tempProjectDirectory.path, + 'build', + 'web', + 'assets', + 'assets', + 'text_asset.txt', + ), + ); + + expect(asset, exists); + + expect( + asset.readAsStringSync(), + equals('ABC'), + reason: + "The original contents of the asset (which should be 'abc') should " + "have been transformed to 'ABC' by the capitalizer_transformer as " + 'configured in the pubspec.', + ); + } finally { + tryToDelete(tempProjectDirectory); + } + }); +} + +void _setUpCapitalizerTransformer(Directory projectDir) { + final Directory targetDir = projectDir.childDirectory('capitalizer_transformer'); + targetDir.createSync(recursive: true); + + targetDir.childFile('pubspec.yaml') + ..createSync() + ..writeAsStringSync(''' +name: capitalizer_transformer +version: 1.0.0 + +environment: + sdk: '>=3.2.0-0 <4.0.0' + +dependencies: + args: ^2.4.2 +'''); + + targetDir.childDirectory('bin').childFile('capitalizer_transformer.dart') + ..createSync(recursive: true) + ..writeAsStringSync(''' +import 'dart:io'; + +import 'package:args/args.dart'; + +void main(List args) { + final ArgParser parser = ArgParser() + ..addOption('input') + ..addOption('output'); + + final ArgResults parsedArgs = parser.parse(args); + + final String inputFilePath = parsedArgs['input'] as String; + final String outputFilePath = parsedArgs['output'] as String; + + final String input = File(inputFilePath).readAsStringSync(); + File(outputFilePath) + ..createSync(recursive: true) + ..writeAsStringSync(input.toUpperCase()); +} +'''); +} diff --git a/packages/flutter_tools/test/integration.shard/plist_parser_test.dart b/packages/flutter_tools/test/integration.shard/plist_parser_test.dart index da1dd99108b0..3b6f5c61d09c 100644 --- a/packages/flutter_tools/test/integration.shard/plist_parser_test.dart +++ b/packages/flutter_tools/test/integration.shard/plist_parser_test.dart @@ -213,4 +213,80 @@ void main() { expect(logger.statusText, isEmpty); expect(logger.errorText, isEmpty); }, skip: !platform.isMacOS); // [intended] requires macos tool chain. + + testWithoutContext('PlistParser.plistJsonContent can parse pbxproj file', () async { + final String xcodeProjectFile = fileSystem.path.join( + getFlutterRoot(), + 'dev', + 'integration_tests', + 'flutter_gallery', + 'ios', + 'Runner.xcodeproj', + 'project.pbxproj' + ); + + final BufferLogger logger = BufferLogger( + terminal: Terminal.test(), + outputPreferences: OutputPreferences(), + ); + + final PlistParser parser = PlistParser( + fileSystem: fileSystem, + processManager: processManager, + logger: logger, + ); + + final String? projectFileAsJson = parser.plistJsonContent(xcodeProjectFile); + expect(projectFileAsJson, isNotNull); + expect(projectFileAsJson, contains('"PRODUCT_NAME":"Flutter Gallery"')); + expect(logger.errorText, isEmpty); + }, skip: !platform.isMacOS); // [intended] requires macos tool chain. + + testWithoutContext('PlistParser.plistJsonContent can parse pbxproj file with unicode and emojis', () async { + String xcodeProjectFile = fileSystem.path.join( + getFlutterRoot(), + 'dev', + 'integration_tests', + 'flutter_gallery', + 'ios', + 'Runner.xcodeproj', + 'project.pbxproj' + ); + + final BufferLogger logger = BufferLogger( + terminal: Terminal.test(), + outputPreferences: OutputPreferences(), + ); + + final PlistParser parser = PlistParser( + fileSystem: fileSystem, + processManager: processManager, + logger: logger, + ); + + xcodeProjectFile = xcodeProjectFile.replaceAll('AppDelegate.m', 'AppDélegate.m'); + xcodeProjectFile = xcodeProjectFile.replaceAll('AppDelegate.h', 'App👍Delegate.h'); + + final String? projectFileAsJson = parser.plistJsonContent(xcodeProjectFile); + expect(projectFileAsJson, isNotNull); + expect(projectFileAsJson, contains('"PRODUCT_NAME":"Flutter Gallery"')); + expect(logger.errorText, isEmpty); + }, skip: !platform.isMacOS); // [intended] requires macos tool chain. + + testWithoutContext('PlistParser.plistJsonContent returns null when errors', () async { + final BufferLogger logger = BufferLogger( + terminal: Terminal.test(), + outputPreferences: OutputPreferences(), + ); + + final PlistParser parser = PlistParser( + fileSystem: fileSystem, + processManager: processManager, + logger: logger, + ); + + final String? projectFileAsJson = parser.plistJsonContent('bad/path'); + expect(projectFileAsJson, isNull); + expect(logger.errorText, isNotEmpty); + }, skip: !platform.isMacOS); // [intended] requires macos tool chain. } diff --git a/packages/flutter_tools/test/integration.shard/swift_package_manager_create_app_test.dart b/packages/flutter_tools/test/integration.shard/swift_package_manager_create_app_test.dart new file mode 100644 index 000000000000..f40e0f6de236 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/swift_package_manager_create_app_test.dart @@ -0,0 +1,151 @@ +// 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_tools/src/base/error_handling_io.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; + +import '../src/common.dart'; +import 'swift_package_manager_utils.dart'; +import 'test_utils.dart'; + +void main() { + final String flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + + final List platforms = ['ios', 'macos']; + for (final String platformName in platforms) { + final List iosLanguages = [ + if (platformName == 'ios') 'objc', + 'swift', + ]; + + for (final String iosLanguage in iosLanguages) { + test('Create $platformName $iosLanguage app with Swift Package Manager disabled', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_create_app_disabled.'); + final String workingDirectoryPath = workingDirectory.path; + try { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final String appDirectoryPath = await SwiftPackageManagerUtils.createApp( + flutterBin, + workingDirectoryPath, + iosLanguage: iosLanguage, + platform: platformName, + options: ['--platforms=$platformName'], + ); + + final File pbxprojFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + expect(pbxprojFile.existsSync(), isTrue); + expect( + pbxprojFile.readAsStringSync().contains('FlutterGeneratedPluginSwiftPackage'), + isFalse, + ); + + final File xcschemeFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + expect(xcschemeFile.existsSync(), isTrue); + expect( + xcschemeFile.readAsStringSync().contains('Run Prepare Flutter Framework Script'), + isFalse, + ); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + ), + ); + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + + test('Create $platformName $iosLanguage app with Swift Package Manager enabled', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_create_app_enabled.'); + final String workingDirectoryPath = workingDirectory.path; + try { + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final String appDirectoryPath = await SwiftPackageManagerUtils.createApp( + flutterBin, + workingDirectoryPath, + iosLanguage: iosLanguage, + platform: platformName, + options: ['--platforms=$platformName'], + ); + + final File pbxprojFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + expect(pbxprojFile.existsSync(), isTrue); + expect( + pbxprojFile.readAsStringSync(), + contains('FlutterGeneratedPluginSwiftPackage'), + ); + + final File xcschemeFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + expect(xcschemeFile.existsSync(), isTrue); + expect( + xcschemeFile.readAsStringSync(), + contains('Run Prepare Flutter Framework Script'), + ); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + ), + ); + + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + } + } +} diff --git a/packages/flutter_tools/test/integration.shard/swift_package_manager_create_plugin_test.dart b/packages/flutter_tools/test/integration.shard/swift_package_manager_create_plugin_test.dart new file mode 100644 index 000000000000..64b9761f97e5 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/swift_package_manager_create_plugin_test.dart @@ -0,0 +1,175 @@ +// 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_tools/src/base/error_handling_io.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; + +import '../src/common.dart'; +import 'swift_package_manager_utils.dart'; +import 'test_utils.dart'; + +void main() { + final String flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + + final List platforms = ['ios', 'macos']; + for (final String platformName in platforms) { + final List iosLanguages = [ + if (platformName == 'ios') 'objc', + 'swift', + ]; + + for (final String iosLanguage in iosLanguages) { + test('Create $platformName $iosLanguage plugin with Swift Package Manager disabled', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_create_plugin_disabled.'); + final String workingDirectoryPath = workingDirectory.path; + try { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final SwiftPackageManagerPlugin createdCocoaPodsPlugin = await SwiftPackageManagerUtils.createPlugin( + flutterBin, + workingDirectoryPath, + platform: platformName, + iosLanguage: iosLanguage, + ); + + final String appDirectoryPath = createdCocoaPodsPlugin.exampleAppPath; + + final File pbxprojFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + expect(pbxprojFile.existsSync(), isTrue); + expect( + pbxprojFile.readAsStringSync().contains('FlutterGeneratedPluginSwiftPackage'), + isFalse, + ); + + final File xcschemeFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + expect(xcschemeFile.existsSync(), isTrue); + expect( + xcschemeFile.readAsStringSync().contains('Run Prepare Flutter Framework Script'), + isFalse, + ); + + final File podspec = fileSystem + .directory(createdCocoaPodsPlugin.pluginPath) + .childDirectory(platformName) + .childFile('${createdCocoaPodsPlugin.pluginName}.podspec'); + expect(podspec.existsSync(), isTrue); + expect(podspec.readAsStringSync(), contains('Classes')); + expect(podspec.readAsStringSync().contains('Sources'), isFalse); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: createdCocoaPodsPlugin, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: createdCocoaPodsPlugin, + ), + ); + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + + test('Create $platformName $iosLanguage plugin with Swift Package Manager enabled', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_create_plugin_enabled.'); + final String workingDirectoryPath = workingDirectory.path; + try { + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final SwiftPackageManagerPlugin createdSwiftPackagePlugin = await SwiftPackageManagerUtils.createPlugin( + flutterBin, + workingDirectoryPath, + platform: platformName, + iosLanguage: iosLanguage, + ); + + final String appDirectoryPath = createdSwiftPackagePlugin.exampleAppPath; + + final File pbxprojFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + expect(pbxprojFile.existsSync(), isTrue); + expect( + pbxprojFile.readAsStringSync(), + contains('FlutterGeneratedPluginSwiftPackage'), + ); + + final File xcschemeFile = fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + expect(xcschemeFile.existsSync(), isTrue); + expect( + xcschemeFile.readAsStringSync(), + contains('Run Prepare Flutter Framework Script'), + ); + + final File podspec = fileSystem + .directory(createdSwiftPackagePlugin.pluginPath) + .childDirectory(platformName) + .childFile('${createdSwiftPackagePlugin.pluginName}.podspec'); + expect(podspec.existsSync(), isTrue); + expect(podspec.readAsStringSync(), contains('Sources')); + expect(podspec.readAsStringSync().contains('Classes'), isFalse); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + swiftPackagePlugin: createdSwiftPackagePlugin, + swiftPackageMangerEnabled: true, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + swiftPackagePlugin: createdSwiftPackagePlugin, + swiftPackageMangerEnabled: true, + ), + ); + + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + } + } +} diff --git a/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart b/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart new file mode 100644 index 000000000000..0f6f585a9972 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/swift_package_manager_test.dart @@ -0,0 +1,409 @@ +// 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_tools/src/base/error_handling_io.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; + +import '../src/common.dart'; +import 'swift_package_manager_utils.dart'; +import 'test_utils.dart'; + +void main() { + final String flutterBin = fileSystem.path.join( + getFlutterRoot(), + 'bin', + 'flutter', + ); + + final List platforms = ['ios', 'macos']; + for (final String platformName in platforms) { + final List iosLanguages = [ + if (platformName == 'ios') 'objc', + 'swift', + ]; + final SwiftPackageManagerPlugin integrationTestPlugin = SwiftPackageManagerUtils.integrationTestPlugin(platformName); + + for (final String iosLanguage in iosLanguages) { + test('Swift Package Manager integration for $platformName with $iosLanguage', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_enabled.'); + final String workingDirectoryPath = workingDirectory.path; + try { + // Create and build an app using the CocoaPods version of + // integration_test. + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + final String appDirectoryPath = await SwiftPackageManagerUtils.createApp( + flutterBin, + workingDirectoryPath, + iosLanguage: iosLanguage, + platform: platformName, + usesSwiftPackageManager: true, + options: ['--platforms=$platformName'], + ); + SwiftPackageManagerUtils.addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin); + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childFile('Podfile') + .existsSync(), + isTrue, + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .existsSync(), + isFalse, + ); + + final SwiftPackageManagerPlugin createdCocoaPodsPlugin = await SwiftPackageManagerUtils.createPlugin( + flutterBin, + workingDirectoryPath, + platform: platformName, + iosLanguage: iosLanguage, + ); + + // Rebuild app with Swift Package Manager enabled, migrating the app and using the Swift Package Manager version of + // integration_test. + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + swiftPackageMangerEnabled: true, + swiftPackagePlugin: integrationTestPlugin, + migrated: true, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + swiftPackageMangerEnabled: true, + swiftPackagePlugin: integrationTestPlugin, + migrated: true, + ), + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childFile('Podfile') + .existsSync(), + isTrue, + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .existsSync(), + isTrue, + ); + + // Build an app using both a CocoaPods and Swift Package Manager plugin. + SwiftPackageManagerUtils.addDependency( + appDirectoryPath: appDirectoryPath, + plugin: createdCocoaPodsPlugin, + ); + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: createdCocoaPodsPlugin, + swiftPackageMangerEnabled: true, + swiftPackagePlugin: integrationTestPlugin, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: createdCocoaPodsPlugin, + swiftPackageMangerEnabled: true, + swiftPackagePlugin: integrationTestPlugin, + ), + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childFile('Podfile') + .existsSync(), + isTrue, + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .existsSync(), + isTrue, + ); + + // Build app again but with Swift Package Manager disabled by config. + // App will now use CocoaPods version of integration_test plugin. + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + await SwiftPackageManagerUtils.cleanApp(flutterBin, appDirectoryPath); + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + ); + + // Build app again but with Swift Package Manager disabled by pubspec. + // App will still use CocoaPods version of integration_test plugin. + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + await SwiftPackageManagerUtils.cleanApp(flutterBin, appDirectoryPath); + SwiftPackageManagerUtils.disableSwiftPackageManagerByPubspec(appDirectoryPath: appDirectoryPath); + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--debug', '-v'], + expectedLines: SwiftPackageManagerUtils.expectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + unexpectedLines: SwiftPackageManagerUtils.unexpectedLines( + platform: platformName, + appDirectoryPath: appDirectoryPath, + cocoaPodsPlugin: integrationTestPlugin, + ), + ); + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + } + + test('Build $platformName-framework with non-module app uses CocoaPods', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_build_framework.'); + final String workingDirectoryPath = workingDirectory.path; + try { + // Create and build an app using the Swift Package Manager version of + // integration_test. + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final String appDirectoryPath = await SwiftPackageManagerUtils.createApp( + flutterBin, + workingDirectoryPath, + iosLanguage: 'swift', + platform: platformName, + usesSwiftPackageManager: true, + options: ['--platforms=$platformName'], + ); + SwiftPackageManagerUtils.addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [platformName, '--config-only', '-v'], + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childFile('Podfile') + .existsSync(), + isFalse, + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory(platformName) + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .existsSync(), + isTrue, + ); + + // Create and build framework using the CocoaPods version of + // integration_test even though Swift Package Manager is enabled. + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [ + '$platformName-framework', + '--no-debug', + '--no-profile', + '-v', + ], + expectedLines: [ + 'Swift Package Manager does not yet support this command. CocoaPods will be used instead.' + ] + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory('build') + .childDirectory(platformName) + .childDirectory('framework') + .childDirectory('Release') + .childDirectory('${integrationTestPlugin.pluginName}.xcframework') + .existsSync(), + isTrue, + ); + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. + } + + test('Build ios-framework with module app uses CocoaPods', () async { + final Directory workingDirectory = fileSystem.systemTempDirectory + .createTempSync('swift_package_manager_build_framework_module.'); + final String workingDirectoryPath = workingDirectory.path; + try { + // Create and build module and framework using the CocoaPods version of + // integration_test even though Swift Package Manager is enabled. + await SwiftPackageManagerUtils.enableSwiftPackageManager(flutterBin, workingDirectoryPath); + + final String appDirectoryPath = await SwiftPackageManagerUtils.createApp( + flutterBin, + workingDirectoryPath, + iosLanguage: 'swift', + platform: 'ios', + usesSwiftPackageManager: true, + options: ['--template=module'], + ); + final SwiftPackageManagerPlugin integrationTestPlugin = SwiftPackageManagerUtils.integrationTestPlugin('ios'); + SwiftPackageManagerUtils.addDependency(appDirectoryPath: appDirectoryPath, plugin: integrationTestPlugin); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: ['ios', '--config-only', '-v'], + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory('.ios') + .childFile('Podfile') + .existsSync(), + isTrue, + ); + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory('.ios') + .childDirectory('Flutter') + .childDirectory('ephemeral') + .childDirectory('Packages') + .childDirectory('FlutterGeneratedPluginSwiftPackage') + .existsSync(), + isFalse, + ); + final File pbxprojFile = fileSystem + .directory(appDirectoryPath) + .childDirectory('.ios') + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + expect(pbxprojFile.existsSync(), isTrue); + expect( + pbxprojFile.readAsStringSync().contains('FlutterGeneratedPluginSwiftPackage'), + isFalse, + ); + final File xcschemeFile = fileSystem + .directory(appDirectoryPath) + .childDirectory('.ios') + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + expect(xcschemeFile.existsSync(), isTrue); + expect( + xcschemeFile.readAsStringSync().contains('Run Prepare Flutter Framework Script'), + isFalse, + ); + + await SwiftPackageManagerUtils.buildApp( + flutterBin, + appDirectoryPath, + options: [ + 'ios-framework', + '--no-debug', + '--no-profile', + '-v', + ], + unexpectedLines: [ + 'Adding Swift Package Manager integration...', + 'Swift Package Manager does not yet support this command. CocoaPods will be used instead.' + ] + ); + + expect( + fileSystem + .directory(appDirectoryPath) + .childDirectory('build') + .childDirectory('ios') + .childDirectory('framework') + .childDirectory('Release') + .childDirectory('${integrationTestPlugin.pluginName}.xcframework') + .existsSync(), + isTrue, + ); + } finally { + await SwiftPackageManagerUtils.disableSwiftPackageManager(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists( + workingDirectory, + recursive: true, + ); + } + }, skip: !platform.isMacOS); // [intended] Swift Package Manager only works on macos. +} diff --git a/packages/flutter_tools/test/integration.shard/swift_package_manager_utils.dart b/packages/flutter_tools/test/integration.shard/swift_package_manager_utils.dart new file mode 100644 index 000000000000..dd23eaed9f17 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/swift_package_manager_utils.dart @@ -0,0 +1,379 @@ +// 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 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +class SwiftPackageManagerUtils { + static Future enableSwiftPackageManager( + String flutterBin, + String workingDirectory, + ) async { + final ProcessResult result = await processManager.run( + [ + flutterBin, + ...getLocalEngineArguments(), + 'config', + '--enable-swift-package-manager', + '-v', + ], + workingDirectory: workingDirectory, + ); + expect( + result.exitCode, + 0, + reason: 'Failed to enable Swift Package Manager: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + verbose: true, + ); + } + + static Future disableSwiftPackageManager( + String flutterBin, + String workingDirectory, + ) async { + final ProcessResult result = await processManager.run( + [ + flutterBin, + ...getLocalEngineArguments(), + 'config', + '--no-enable-swift-package-manager', + '-v', + ], + workingDirectory: workingDirectory, + ); + expect( + result.exitCode, + 0, + reason: 'Failed to disable Swift Package Manager: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + verbose: true, + ); + } + + static Future createApp( + String flutterBin, + String workingDirectory, { + required String platform, + required String iosLanguage, + required List options, + bool usesSwiftPackageManager = false, + }) async { + final String appTemplateType = usesSwiftPackageManager ? 'spm' : 'default'; + + final String appName = '${platform}_${iosLanguage}_${appTemplateType}_app'; + final ProcessResult result = await processManager.run( + [ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--org', + 'io.flutter.devicelab', + '-i', + iosLanguage, + ...options, + appName, + ], + workingDirectory: workingDirectory, + ); + + expect( + result.exitCode, + 0, + reason: 'Failed to create app: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + + return fileSystem.path.join( + workingDirectory, + appName, + ); + } + + static Future buildApp( + String flutterBin, + String workingDirectory, { + required List options, + List? expectedLines, + List? unexpectedLines, + }) async { + final List remainingExpectedLines = expectedLines ?? []; + final List unexpectedLinesFound = []; + final List command = [ + flutterBin, + ...getLocalEngineArguments(), + 'build', + ...options, + ]; + + final ProcessResult result = await processManager.run( + command, + workingDirectory: workingDirectory, + ); + + final List stdout = LineSplitter.split(result.stdout.toString()).toList(); + final List stderr = LineSplitter.split(result.stderr.toString()).toList(); + final List output = stdout + stderr; + for (final String line in output) { + // Remove "[ +3 ms] " prefix + String trimmedLine = line.trim(); + if (trimmedLine.startsWith('[')) { + final int prefixEndIndex = trimmedLine.indexOf(']'); + if (prefixEndIndex > 0) { + trimmedLine = trimmedLine + .substring(prefixEndIndex + 1, trimmedLine.length) + .trim(); + } + } + remainingExpectedLines.remove(trimmedLine); + remainingExpectedLines.removeWhere((Pattern expectedLine) => trimmedLine.contains(expectedLine)); + if (unexpectedLines != null && unexpectedLines.contains(trimmedLine)) { + unexpectedLinesFound.add(trimmedLine); + } + } + expect( + result.exitCode, + 0, + reason: 'Failed to build app for "${command.join(' ')}":\n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + expect( + remainingExpectedLines, + isEmpty, + reason: 'Did not find expected lines for "${command.join(' ')}":\n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + expect( + unexpectedLinesFound, + isEmpty, + reason: 'Found unexpected lines for "${command.join(' ')}":\n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + } + + static Future cleanApp(String flutterBin, String workingDirectory) async { + final ProcessResult result = await processManager.run( + [ + flutterBin, + ...getLocalEngineArguments(), + 'clean', + ], + workingDirectory: workingDirectory, + ); + expect( + result.exitCode, + 0, + reason: 'Failed to clean app: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + } + + static Future createPlugin( + String flutterBin, + String workingDirectory, { + required String platform, + required String iosLanguage, + bool usesSwiftPackageManager = false, + }) async { + final String dependencyManager = usesSwiftPackageManager ? 'spm' : 'cocoapods'; + + // Create plugin + final String pluginName = '${platform}_${iosLanguage}_${dependencyManager}_plugin'; + final ProcessResult result = await processManager.run( + [ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--org', + 'io.flutter.devicelab', + '--template=plugin', + '--platforms=$platform', + '-i', + iosLanguage, + pluginName, + ], + workingDirectory: workingDirectory, + ); + + expect( + result.exitCode, + 0, + reason: 'Failed to create plugin: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + + final Directory pluginDirectory = fileSystem.directory( + fileSystem.path.join(workingDirectory, pluginName), + ); + + return SwiftPackageManagerPlugin( + pluginName: pluginName, + pluginPath: pluginDirectory.path, + platform: platform, + ); + } + + static void addDependency({ + required SwiftPackageManagerPlugin plugin, + required String appDirectoryPath, + }) { + final File pubspec = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'pubspec.yaml'), + ); + final String pubspecContent = pubspec.readAsStringSync(); + pubspec.writeAsStringSync( + pubspecContent.replaceFirst( + '\ndependencies:\n', + '\ndependencies:\n ${plugin.pluginName}:\n path: ${plugin.pluginPath}\n', + ), + ); + } + + static void disableSwiftPackageManagerByPubspec({ + required String appDirectoryPath, + }) { + final File pubspec = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'pubspec.yaml'), + ); + final String pubspecContent = pubspec.readAsStringSync(); + pubspec.writeAsStringSync( + pubspecContent.replaceFirst( + '\n# The following section is specific to Flutter packages.\nflutter:\n', + '\n# The following section is specific to Flutter packages.\nflutter:\n disable-swift-package-manager: true', + ), + ); + } + + static SwiftPackageManagerPlugin integrationTestPlugin(String platform) { + final String flutterRoot = getFlutterRoot(); + return SwiftPackageManagerPlugin( + platform: platform, + pluginName: + (platform == 'ios') ? 'integration_test' : 'integration_test_macos', + pluginPath: (platform == 'ios') + ? fileSystem.path.join(flutterRoot, 'packages', 'integration_test') + : fileSystem.path.join(flutterRoot, 'packages', 'integration_test', 'integration_test_macos'), + ); + } + + static List expectedLines({ + required String platform, + required String appDirectoryPath, + SwiftPackageManagerPlugin? cocoaPodsPlugin, + SwiftPackageManagerPlugin? swiftPackagePlugin, + bool swiftPackageMangerEnabled = false, + bool migrated = false, + }) { + final String frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS'; + final String appPlatformDirectoryPath = fileSystem.path.join( + appDirectoryPath, + platform, + ); + + final List expectedLines = []; + if (swiftPackageMangerEnabled) { + expectedLines.addAll([ + 'FlutterGeneratedPluginSwiftPackage: $appPlatformDirectoryPath/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage', + "➜ Explicit dependency on target 'FlutterGeneratedPluginSwiftPackage' in project 'FlutterGeneratedPluginSwiftPackage'", + ]); + } + if (swiftPackagePlugin != null) { + // If using a Swift Package plugin, but Swift Package Manager is not enabled, it falls back to being used as a CocoaPods plugin. + if (swiftPackageMangerEnabled) { + expectedLines.addAll([ + RegExp('${swiftPackagePlugin.pluginName}: [/private]*${swiftPackagePlugin.pluginPath}/$platform/${swiftPackagePlugin.pluginName} @ local'), + "➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project '${swiftPackagePlugin.pluginName}'", + ]); + } else { + expectedLines.addAll([ + '-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)', + "➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project 'Pods'", + ]); + } + } + if (cocoaPodsPlugin != null) { + expectedLines.addAll([ + 'Running pod install...', + '-> Installing $frameworkName (1.0.0)', + '-> Installing ${cocoaPodsPlugin.pluginName} (0.0.1)', + "Target 'Pods-Runner' in project 'Pods'", + "➜ Explicit dependency on target '$frameworkName' in project 'Pods'", + "➜ Explicit dependency on target '${cocoaPodsPlugin.pluginName}' in project 'Pods'", + ]); + } + if (migrated) { + expectedLines.addAll([ + 'Adding Swift Package Manager integration...', + 'Running pod install...', + "Target 'Pods-Runner' in project 'Pods'", + ]); + } + return expectedLines; + } + + static List unexpectedLines({ + required String platform, + required String appDirectoryPath, + SwiftPackageManagerPlugin? cocoaPodsPlugin, + SwiftPackageManagerPlugin? swiftPackagePlugin, + bool swiftPackageMangerEnabled = false, + bool migrated = false, + }) { + final String frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS'; + final List unexpectedLines = []; + if (cocoaPodsPlugin == null && !migrated) { + unexpectedLines.addAll([ + 'Running pod install...', + '-> Installing $frameworkName (1.0.0)', + "Target 'Pods-Runner' in project 'Pods'", + ]); + } + if (swiftPackagePlugin != null) { + if (swiftPackageMangerEnabled) { + unexpectedLines.addAll([ + '-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)', + "➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project 'Pods'", + ]); + } else { + unexpectedLines.addAll([ + '${swiftPackagePlugin.pluginName}: ${swiftPackagePlugin.pluginPath}/$platform/${swiftPackagePlugin.pluginName} @ local', + "➜ Explicit dependency on target '${swiftPackagePlugin.pluginName}' in project '${swiftPackagePlugin.pluginName}'", + ]); + } + } + if (!migrated) { + unexpectedLines.addAll([ + 'Adding Swift Package Manager integration...', + ]); + } + return unexpectedLines; + } +} + +class SwiftPackageManagerPlugin { + SwiftPackageManagerPlugin({ + required this.pluginName, + required this.pluginPath, + required this.platform, + }); + + final String pluginName; + final String pluginPath; + final String platform; + String get exampleAppPath => fileSystem.path.join(pluginPath, 'example'); + String get exampleAppPlatformPath => fileSystem.path.join(exampleAppPath, platform); +} diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index aa874cfeddab..b812679f9862 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -315,6 +315,11 @@ class FakePlistParser implements PlistParser { @override String? plistXmlContent(String plistFilePath) => throw UnimplementedError(); + @override + String? plistJsonContent(String filePath, {bool sorted = false}) { + throw UnimplementedError(); + } + @override Map parseFile(String plistFilePath) { return _underlyingValues; @@ -472,6 +477,7 @@ class TestFeatureFlags implements FeatureFlags { this.isCliAnimationEnabled = true, this.isNativeAssetsEnabled = false, this.isPreviewDeviceEnabled = false, + this.isSwiftPackageManagerEnabled = false, }); @override @@ -507,6 +513,9 @@ class TestFeatureFlags implements FeatureFlags { @override final bool isPreviewDeviceEnabled; + @override + final bool isSwiftPackageManagerEnabled; + @override bool isEnabled(Feature feature) { return switch (feature) { diff --git a/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart b/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart index fab7d5ba1587..85b2b6690f06 100644 --- a/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart +++ b/packages/flutter_tools/test/web.shard/test_data/hot_reload_index_html_samples.dart @@ -6,7 +6,7 @@ // by Flutter Web users. // This should be somewhat kept in sync with the different index.html files present // in `flutter/dev/integration_tests/web/web`. -// @see https://github.com/flutter/flutter/tree/master/dev/integration_tests/web/web +// @see https://github.com/flutter/flutter/tree/main/dev/integration_tests/web/web /// index_with_flutterjs_entrypoint_loaded.html String indexHtmlFlutterJsCallback = _generateFlutterJsIndexHtml(''' diff --git a/packages/flutter_web_plugins/pubspec.yaml b/packages/flutter_web_plugins/pubspec.yaml index 8ea0170cdc69..c80e460b3563 100644 --- a/packages/flutter_web_plugins/pubspec.yaml +++ b/packages/flutter_web_plugins/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -33,7 +33,7 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b75d +# PUBSPEC CHECKSUM: 8a61 diff --git a/packages/fuchsia_remote_debug_protocol/pubspec.yaml b/packages/fuchsia_remote_debug_protocol/pubspec.yaml index 67e3cd6e9319..81584d263add 100644 --- a/packages/fuchsia_remote_debug_protocol/pubspec.yaml +++ b/packages/fuchsia_remote_debug_protocol/pubspec.yaml @@ -8,15 +8,15 @@ environment: dependencies: process: 5.0.2 - vm_service: 14.2.0 + vm_service: 14.2.1 file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - meta: 1.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.25.2 + test: 1.25.4 _fe_analyzer_shared: 67.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 6.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -27,7 +27,7 @@ dev_dependencies: convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -51,8 +51,8 @@ dev_dependencies: stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web: 0.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,4 +64,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 2763 +# PUBSPEC CHECKSUM: bf6a diff --git a/packages/integration_test/README.md b/packages/integration_test/README.md index 7aa350394f84..6ebec272911c 100644 --- a/packages/integration_test/README.md +++ b/packages/integration_test/README.md @@ -50,7 +50,7 @@ Future main() => integrationDriver(); You can also use different driver scripts to customize the behavior of the app under test. For example, `FlutterDriver` can also be parameterized with different [options](https://api.flutter.dev/flutter/flutter_driver/FlutterDriver/connect.html). -See the [extended driver](https://github.com/flutter/flutter/blob/master/packages/integration_test/example/test_driver/extended_integration_test.dart) for an example. +See the [extended driver](https://github.com/flutter/flutter/blob/main/packages/integration_test/example/test_driver/extended_integration_test.dart) for an example. ### Package Structure @@ -68,7 +68,7 @@ test_driver/ integration_test.dart ``` -[Example](https://github.com/flutter/flutter/tree/master/packages/integration_test/example) +[Example](https://github.com/flutter/flutter/tree/main/packages/integration_test/example) ## Using Flutter Driver to Run Tests diff --git a/packages/integration_test/example/pubspec.yaml b/packages/integration_test/example/pubspec.yaml index 501f0c7f791e..91366cc0e13a 100644 --- a/packages/integration_test/example/pubspec.yaml +++ b/packages/integration_test/example/pubspec.yaml @@ -10,13 +10,13 @@ dependencies: flutter: sdk: flutter - cupertino_icons: 1.0.6 + cupertino_icons: 1.0.8 web: 0.5.1 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: @@ -28,7 +28,7 @@ dev_dependencies: sdk: flutter integration_test_macos: path: ../integration_test_macos - test: 1.25.2 + test: 1.25.4 pedantic: 1.11.1 # For information on the generic Dart part of this file, see the @@ -45,7 +45,7 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - frontend_server_client: 3.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,10 +74,10 @@ dev_dependencies: string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 14.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -87,4 +87,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: cdc0 +# PUBSPEC CHECKSUM: 7cc9 diff --git a/packages/integration_test/integration_test_macos/ios/integration_test_macos.podspec b/packages/integration_test/integration_test_macos/ios/integration_test_macos.podspec index 6034ec28510d..38da23cd7e43 100644 --- a/packages/integration_test/integration_test_macos/ios/integration_test_macos.podspec +++ b/packages/integration_test/integration_test_macos/ios/integration_test_macos.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| No-op implementation of integration to avoid build issues on iOS. See https://github.com/flutter/flutter/issues/39659 DESC - s.homepage = 'https://github.com/flutter/flutter/tree/master/packages/integration_test/integration_test_macos' + s.homepage = 'https://github.com/flutter/flutter/tree/main/packages/integration_test/integration_test_macos' s.license = { :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } s.source = { :path => '.' } diff --git a/packages/integration_test/integration_test_macos/macos/integration_test_macos.podspec b/packages/integration_test/integration_test_macos/macos/integration_test_macos.podspec index a8b6e233901a..01ce5e85d0a7 100644 --- a/packages/integration_test/integration_test_macos/macos/integration_test_macos.podspec +++ b/packages/integration_test/integration_test_macos/macos/integration_test_macos.podspec @@ -8,17 +8,17 @@ Pod::Spec.new do |s| s.description = <<-DESC Runs tests that use the flutter_test API as integration tests on macOS. DESC - s.homepage = 'https://github.com/flutter/flutter/tree/master/packages/integration_test/integration_test_macos' + s.homepage = 'https://github.com/flutter/flutter/tree/main/packages/integration_test/integration_test_macos' s.license = { :type => 'BSD', :text => <<-LICENSE Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. LICENSE } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/flutter/tree/master/packages/integration_test/integration_test_macos' } - s.source_files = 'Classes/**/*' + s.source = { :http => 'https://github.com/flutter/flutter/tree/main/packages/integration_test/integration_test_macos' } + s.source_files = 'integration_test_macos/Sources/integration_test_macos/**/*' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' + s.platform = :osx, '10.14' s.swift_version = '5.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end diff --git a/packages/integration_test/integration_test_macos/macos/integration_test_macos/Package.swift b/packages/integration_test/integration_test_macos/macos/integration_test_macos/Package.swift new file mode 100644 index 000000000000..9ef9c76b9b26 --- /dev/null +++ b/packages/integration_test/integration_test_macos/macos/integration_test_macos/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "integration_test_macos", + platforms: [ + .macOS("10.14"), + ], + products: [ + .library(name: "integration-test-macos", targets: ["integration_test_macos"]), + ], + targets: [ + .target( + name: "integration_test_macos", + resources: [ + .process("Resources"), + ] + ), + ] +) diff --git a/packages/integration_test/integration_test_macos/macos/Classes/IntegrationTestPlugin.swift b/packages/integration_test/integration_test_macos/macos/integration_test_macos/Sources/integration_test_macos/IntegrationTestPlugin.swift similarity index 100% rename from packages/integration_test/integration_test_macos/macos/Classes/IntegrationTestPlugin.swift rename to packages/integration_test/integration_test_macos/macos/integration_test_macos/Sources/integration_test_macos/IntegrationTestPlugin.swift diff --git a/packages/integration_test/integration_test_macos/macos/integration_test_macos/Sources/integration_test_macos/Resources/.gitkeep b/packages/integration_test/integration_test_macos/macos/integration_test_macos/Sources/integration_test_macos/Resources/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/integration_test/integration_test_macos/pubspec.yaml b/packages/integration_test/integration_test_macos/pubspec.yaml index a569b832d8b4..f67bee1a83a3 100644 --- a/packages/integration_test/integration_test_macos/pubspec.yaml +++ b/packages/integration_test/integration_test_macos/pubspec.yaml @@ -1,7 +1,7 @@ name: integration_test_macos description: Desktop implementation of integration_test plugin version: 0.0.1+1 -homepage: https://github.com/flutter/flutter/tree/master/packages/integration_test/integration_test_macos +homepage: https://github.com/flutter/flutter/tree/main/packages/integration_test/integration_test_macos flutter: plugin: @@ -19,10 +19,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.18.0 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.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" dev_dependencies: pedantic: 1.11.1 -# PUBSPEC CHECKSUM: b487 +# PUBSPEC CHECKSUM: 4789 diff --git a/packages/integration_test/ios/integration_test.podspec b/packages/integration_test/ios/integration_test.podspec index d2a8440dcc6d..c5848dd85242 100644 --- a/packages/integration_test/ios/integration_test.podspec +++ b/packages/integration_test/ios/integration_test.podspec @@ -8,15 +8,15 @@ Pod::Spec.new do |s| s.description = <<-DESC Runs tests that use the flutter_test API as integration tests. DESC - s.homepage = 'https://github.com/flutter/flutter/tree/master/packages/integration_test' + s.homepage = 'https://github.com/flutter/flutter/tree/main/packages/integration_test' s.license = { :type => 'BSD', :text => <<-LICENSE Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. LICENSE } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } - s.source = { :http => 'https://github.com/flutter/flutter/tree/master/packages/integration_test' } - s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' + s.source = { :http => 'https://github.com/flutter/flutter/tree/main/packages/integration_test' } + s.source_files = 'integration_test/Sources/integration_test/**/*.{h,m}' + s.public_header_files = 'integration_test/Sources/integration_test/**/*.h' s.dependency 'Flutter' s.ios.framework = 'UIKit' diff --git a/packages/integration_test/ios/integration_test/Package.swift b/packages/integration_test/ios/integration_test/Package.swift new file mode 100644 index 000000000000..b9bd1a9a4507 --- /dev/null +++ b/packages/integration_test/ios/integration_test/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "integration_test", + platforms: [ + .iOS("12.0"), + ], + products: [ + .library(name: "integration-test", targets: ["integration_test"]), + ], + targets: [ + .target( + name: "integration_test", + resources: [ + .process("Resources"), + ], + cSettings: [ + .headerSearchPath("include/integration_test"), + ] + ), + ] +) diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.m b/packages/integration_test/ios/integration_test/Sources/integration_test/FLTIntegrationTestRunner.m similarity index 100% rename from packages/integration_test/ios/Classes/FLTIntegrationTestRunner.m rename to packages/integration_test/ios/integration_test/Sources/integration_test/FLTIntegrationTestRunner.m diff --git a/packages/integration_test/ios/Classes/IntegrationTestIosTest.m b/packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestIosTest.m similarity index 100% rename from packages/integration_test/ios/Classes/IntegrationTestIosTest.m rename to packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestIosTest.m diff --git a/packages/integration_test/ios/Classes/IntegrationTestPlugin.m b/packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestPlugin.m similarity index 100% rename from packages/integration_test/ios/Classes/IntegrationTestPlugin.m rename to packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestPlugin.m diff --git a/packages/integration_test/ios/integration_test/Sources/integration_test/Resources/.gitkeep b/packages/integration_test/ios/integration_test/Sources/integration_test/Resources/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/integration_test/ios/Classes/FLTIntegrationTestRunner.h b/packages/integration_test/ios/integration_test/Sources/integration_test/include/FLTIntegrationTestRunner.h similarity index 100% rename from packages/integration_test/ios/Classes/FLTIntegrationTestRunner.h rename to packages/integration_test/ios/integration_test/Sources/integration_test/include/FLTIntegrationTestRunner.h diff --git a/packages/integration_test/ios/Classes/IntegrationTestIosTest.h b/packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestIosTest.h similarity index 100% rename from packages/integration_test/ios/Classes/IntegrationTestIosTest.h rename to packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestIosTest.h diff --git a/packages/integration_test/ios/Classes/IntegrationTestPlugin.h b/packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestPlugin.h similarity index 100% rename from packages/integration_test/ios/Classes/IntegrationTestPlugin.h rename to packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestPlugin.h diff --git a/packages/integration_test/lib/common.dart b/packages/integration_test/lib/common.dart index beae6f4635a9..93ebb43e8a51 100644 --- a/packages/integration_test/lib/common.dart +++ b/packages/integration_test/lib/common.dart @@ -106,16 +106,10 @@ class Response { /// Create a list of Strings from [_failureDetails]. List _failureDetailsAsString() { - final List list = []; - if (_failureDetails == null || _failureDetails.isEmpty) { - return list; - } - - for (final Failure failure in _failureDetails) { - list.add(failure.toJson()); - } - - return list; + return [ + if (_failureDetails != null) + for (final Failure failure in _failureDetails) failure.toJson(), + ]; } /// Creates a [Failure] list using a json response. diff --git a/packages/integration_test/lib/fix_data/README.md b/packages/integration_test/lib/fix_data/README.md index 748818fc6128..05c275d0b00f 100644 --- a/packages/integration_test/lib/fix_data/README.md +++ b/packages/integration_test/lib/fix_data/README.md @@ -38,7 +38,7 @@ format do not break Flutter. See [tools/bots/flutter/analyze_flutter_flutter.sh](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_flutter.sh) for where the flutter fix tests are invoked for the dart repo. -See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) +See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/main/dev/bots/test.dart) for where the flutter fix tests are invoked for the flutter/flutter repo. When possible, please coordinate changes to this directory that might affect the diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index d9dcf4781999..2088fe0b284d 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: flutter_test: sdk: flutter path: 1.9.0 - vm_service: 14.2.0 + vm_service: 14.2.1 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -27,14 +27,14 @@ dependencies: leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" matcher: 0.12.16+1 # 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.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.14.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # 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.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.1 # 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" webdriver: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -47,4 +47,4 @@ flutter: ios: pluginClass: IntegrationTestPlugin -# PUBSPEC CHECKSUM: 8593 +# PUBSPEC CHECKSUM: 1997