From edcf4c868b98ae0deea8434418192ee0ad110243 Mon Sep 17 00:00:00 2001 From: Mairramer <50643541+Mairramer@users.noreply.github.com> Date: Wed, 22 Jul 2026 06:12:23 +0000 Subject: [PATCH 001/147] Add barrierBuilder support to showDialog and showGeneralDialog (#187992) Related to https://github.com/flutter/flutter/issues/21039 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Tong Mu --- packages/flutter/lib/src/widgets/routes.dart | 81 +++++++ .../flutter/test/widgets/routes_test.dart | 199 +++++++++++------- 2 files changed, 208 insertions(+), 72 deletions(-) diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 5c0b9b127fa4c..ce419f0780f4c 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -2599,6 +2599,7 @@ class RawDialogRoute extends PopupRoute { String? barrierLabel, Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, + this.barrierBuilder, super.settings, super.requestFocus, this.anchorPoint, @@ -2632,6 +2633,11 @@ class RawDialogRoute extends PopupRoute { final RouteTransitionsBuilder? _transitionBuilder; + /// The [barrierBuilder] argument is used to define how the route's modal + /// barrier is built. If not null, this builder is used to wrap or replace + /// the default [ModalBarrier]. + final RouteBarrierBuilder? barrierBuilder; + /// {@macro flutter.widgets.DisplayFeatureSubScreen.anchorPoint} final Offset? anchorPoint; @@ -2675,6 +2681,26 @@ class RawDialogRoute extends PopupRoute { } return _transitionBuilder(context, animation, secondaryAnimation, child); } + + @override + Widget buildModalBarrier() { + final Widget barrier = super.buildModalBarrier(); + if (barrierBuilder != null) { + return Builder( + builder: (BuildContext context) => barrierBuilder!( + context, + RouteBarrierDetails( + animation: animation!, + barrierColor: barrierColor, + barrierLabel: barrierLabel, + barrierDismissible: barrierDismissible, + ), + barrier, + ), + ); + } + return barrier; + } } /// Displays a dialog above the current contents of the app. @@ -2720,6 +2746,10 @@ class RawDialogRoute extends PopupRoute { /// and leaves off the screen. By default, the transition is a linear fade of /// the page's contents. /// +/// The `barrierBuilder` argument is used to define how the route's modal +/// barrier is built. If not null, this builder is used to wrap or replace +/// the default [ModalBarrier]. +/// /// The `routeSettings` will be used in the construction of the dialog's route. /// See [RouteSettings] for more details. /// @@ -2765,6 +2795,7 @@ Future showGeneralDialog({ Color barrierColor = const Color(0x80000000), Duration transitionDuration = const Duration(milliseconds: 200), RouteTransitionsBuilder? transitionBuilder, + RouteBarrierBuilder? barrierBuilder, bool useRootNavigator = true, bool fullscreenDialog = false, RouteSettings? routeSettings, @@ -2780,6 +2811,7 @@ Future showGeneralDialog({ barrierColor: barrierColor, transitionDuration: transitionDuration, transitionBuilder: transitionBuilder, + barrierBuilder: barrierBuilder, settings: routeSettings, anchorPoint: anchorPoint, requestFocus: requestFocus, @@ -2822,6 +2854,55 @@ typedef RouteTransitionsBuilder = Widget child, ); +/// Configuration details for a custom modal barrier. +/// +/// Passed to a [RouteBarrierBuilder] by the routing framework to provide +/// the ambient variables associated with the route's modal barrier. +class RouteBarrierDetails { + /// Creates an object that contains the configuration for a modal barrier. + const RouteBarrierDetails({ + required this.animation, + this.barrierColor, + this.barrierLabel, + required this.barrierDismissible, + }); + + /// An animation that drives the route's transition. + /// + /// Typically used to animate the barrier's opacity from 0.0 to 1.0 when the + /// route is pushed. + final Animation animation; + + /// The color to paint behind the route. + /// + /// If null, the barrier will be transparent. + final Color? barrierColor; + + /// The semantic label used for the barrier. + /// + /// This is read out by accessibility tools (like TalkBack or VoiceOver) + /// when the barrier is focused to indicate what will happen when it is interacted with. + final String? barrierLabel; + + /// Whether touching the barrier will pop the current route off the [Navigator]. + /// + /// If true, the route will be dismissed when the barrier is tapped. + final bool barrierDismissible; +} + +/// Signature for the function that builds a custom modal barrier for a route. +/// +/// Used by [RawDialogRoute.barrierBuilder] and [showGeneralDialog] to wrap or +/// replace the default modal barrier. +/// +/// The `barrier` parameter is the default [ModalBarrier] (or [AnimatedModalBarrier]) +/// constructed by the framework. Custom builders should typically return a widget +/// that wraps this `barrier` (for instance, with a [Padding] or a [BackdropFilter]), +/// rather than replacing it entirely, to preserve the built-in semantics and +/// gestures. +typedef RouteBarrierBuilder = + Widget Function(BuildContext context, RouteBarrierDetails details, Widget barrier); + /// A callback type for informing that a navigation pop has been invoked, /// whether or not it was handled successfully. /// diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart index 81a2aedae03e8..4378907005679 100644 --- a/packages/flutter/test/widgets/routes_test.dart +++ b/packages/flutter/test/widgets/routes_test.dart @@ -1846,81 +1846,77 @@ void main() { expect(modalBarrierAnimation.value, _white); }); - testWidgets( - 'modal route semantics order', - (WidgetTester tester) async { - // Regression test for https://github.com/flutter/flutter/issues/46625. - final semantics = SemanticsTester(tester); - await tester.pumpWidget( - TestWidgetsApp( - home: Builder( - builder: (BuildContext context) { - return Center( - child: TestButton( - child: const Text('X'), - onPressed: () { - Navigator.of(context).push( - _TestDialogRouteWithCustomBarrierCurve( - child: const Text('Hello World'), - barrierLabel: 'test label', - barrierCurve: Curves.linear, - ), - ); - }, - ), - ); - }, - ), + testWidgets('modal route semantics order', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/46625. + final semantics = SemanticsTester(tester); + await tester.pumpWidget( + TestWidgetsApp( + home: Builder( + builder: (BuildContext context) { + return Center( + child: TestButton( + child: const Text('X'), + onPressed: () { + Navigator.of(context).push( + _TestDialogRouteWithCustomBarrierCurve( + child: const Text('Hello World'), + barrierLabel: 'test label', + barrierCurve: Curves.linear, + ), + ); + }, + ), + ); + }, ), - ); + ), + ); - await tester.tap(find.text('X')); - await tester.pumpAndSettle(); - expect(find.text('Hello World'), findsOneWidget); - - final expectedSemantics = TestSemantics.root( - children: [ - TestSemantics.rootChild( - id: 1, - rect: TestSemantics.fullScreen, - children: [ - TestSemantics( - id: 6, - rect: TestSemantics.fullScreen, - children: [ - TestSemantics( - id: 7, - rect: TestSemantics.fullScreen, - flags: [SemanticsFlag.scopesRoute], - children: [ - TestSemantics( - id: 8, - label: 'Hello World', - rect: TestSemantics.fullScreen, - textDirection: TextDirection.ltr, - ), - ], - ), - ], - ), - // Modal barrier is put after modal scope - TestSemantics( - id: 5, - rect: TestSemantics.fullScreen, - actions: [SemanticsAction.tap, SemanticsAction.dismiss], - label: 'test label', - textDirection: TextDirection.ltr, - ), - ], - ), - ], - ); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + expect(find.text('Hello World'), findsOneWidget); + + final expectedSemantics = TestSemantics.root( + children: [ + TestSemantics.rootChild( + id: 1, + rect: TestSemantics.fullScreen, + children: [ + TestSemantics( + id: 6, + rect: TestSemantics.fullScreen, + children: [ + TestSemantics( + id: 7, + rect: TestSemantics.fullScreen, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 8, + label: 'Hello World', + rect: TestSemantics.fullScreen, + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + // Modal barrier is put after modal scope + TestSemantics( + id: 5, + rect: TestSemantics.fullScreen, + actions: [SemanticsAction.tap, SemanticsAction.dismiss], + label: 'test label', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ); - expect(semantics, hasSemantics(expectedSemantics)); - semantics.dispose(); - }, - variant: const TargetPlatformVariant({TargetPlatform.iOS}), - ); + expect(semantics, hasSemantics(expectedSemantics)); + semantics.dispose(); + }, variant: const TargetPlatformVariant({TargetPlatform.iOS})); testWidgets('focus traversal is correct when popping multiple pages simultaneously', ( WidgetTester tester, @@ -2823,6 +2819,65 @@ void main() { expect(FocusScope.of(tester.element(find.text('dialog'))).hasFocus, false); expect(focusNode.hasFocus, true); }); + + testWidgets('showGeneralDialog applies custom barrierBuilder', (WidgetTester tester) async { + const expectedPadding = 12.0; + const barrierKey = ValueKey('custom-barrier-padding'); + RouteBarrierDetails? capturedDetails; + + await tester.pumpWidget( + TestWidgetsApp( + home: Builder( + builder: (BuildContext context) { + return TestButton( + onPressed: () { + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: 'barrier_label', + transitionDuration: Duration.zero, + barrierColor: _green, + barrierBuilder: + (BuildContext context, RouteBarrierDetails details, Widget barrier) { + capturedDetails = details; + return Padding( + key: barrierKey, + padding: const EdgeInsets.all(expectedPadding), + child: barrier, + ); + }, + pageBuilder: + ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) => const SizedBox(), + ); + }, + child: const Text('Show Dialog'), + ); + }, + ), + ), + ); + + // Open the dialog. + await tester.tap(find.byType(TestButton)); + await tester.pumpAndSettle(); + + final Padding paddingWidget = tester.widget(find.byKey(barrierKey)); + expect(paddingWidget.padding, const EdgeInsets.all(expectedPadding)); + + final ModalBarrier barrierWidget = tester.widget( + find.descendant(of: find.byKey(barrierKey), matching: find.byType(ModalBarrier)), + ); + expect(barrierWidget.color, _green); + + expect(capturedDetails, isNotNull); + expect(capturedDetails!.barrierColor, _green); + expect(capturedDetails!.barrierDismissible, true); + expect(capturedDetails!.barrierLabel, 'barrier_label'); + }); } double _getOpacity(GlobalKey key, WidgetTester tester) { From 468c2c9d78a9c74ed94ccdbb50c8361c20e3d024 Mon Sep 17 00:00:00 2001 From: Mohellebi Abdessalem <116356835+AbdeMohlbi@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:51:27 +0000 Subject: [PATCH 002/147] Clean android engine/embedding tests (#189276) This change mostly removes unused imports and fixes the linter warnings about asserts Screenshot 2026-07-10 at 17 47 51 Screenshot 2026-07-10 at 17 36 03 Screenshot 2026-07-10 at 17 44 57 Screenshot 2026-07-10 at 17 34 16 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../android/FlutterFragmentActivityTest.java | 8 +- .../android/KeyChannelResponderTest.java | 2 +- .../embedding/engine/FlutterEngineTest.java | 5 +- .../engine/dart/DartExecutorTest.java | 4 +- .../engine/dart/DartMessengerTest.java | 2 +- ...PlayStoreDeferredComponentManagerTest.java | 114 +++++++-------- .../engine/renderer/FlutterRendererTest.java | 10 +- .../SurfaceTextureSurfaceProducerTest.java | 4 +- .../renderer/SurfaceTextureWrapperTest.java | 1 - .../systemchannels/PlatformChannelTest.java | 2 +- .../RestorationChannelTest.java | 3 +- .../common/StandardMessageCodecTest.java | 2 +- .../common/StandardMethodCodecTest.java | 6 +- .../editing/InputConnectionAdaptorTest.java | 132 +++++++++--------- .../plugin/mouse/MouseCursorPluginTest.java | 2 +- .../plugin/platform/PlatformPluginTest.java | 6 +- .../platform/PlatformViewWrapperTest.java | 2 - .../PlatformViewsController2Test.java | 8 +- .../platform/PlatformViewsControllerTest.java | 46 +++--- .../platform/SingleViewPresentationTest.java | 1 - ...ceTexturePlatformViewRenderTargetTest.java | 2 - .../plugin/text/ProcessTextPluginTest.java | 6 +- 22 files changed, 182 insertions(+), 186 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 91f5187d16c83..5d310974d916a 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -70,7 +70,7 @@ public void tearDown() { @Test public void createFlutterFragment_defaultRenderModeSurface() { final FlutterFragmentActivity activity = new FakeFlutterFragmentActivity(); - assertEquals(activity.createFlutterFragment().getRenderMode(), RenderMode.surface); + assertEquals(RenderMode.surface, activity.createFlutterFragment().getRenderMode()); } @Test @@ -82,7 +82,7 @@ protected BackgroundMode getBackgroundMode() { return BackgroundMode.transparent; } }; - assertEquals(activity.createFlutterFragment().getRenderMode(), RenderMode.texture); + assertEquals(RenderMode.texture, activity.createFlutterFragment().getRenderMode()); } @Test @@ -94,7 +94,7 @@ protected RenderMode getRenderMode() { return RenderMode.texture; } }; - assertEquals(activity.createFlutterFragment().getRenderMode(), RenderMode.texture); + assertEquals(RenderMode.texture, activity.createFlutterFragment().getRenderMode()); } @Test @@ -107,7 +107,7 @@ public String getDartEntrypointLibraryUri() { } }; assertEquals( - activity.createFlutterFragment().getDartEntrypointLibraryUri(), "package:foo/bar.dart"); + "package:foo/bar.dart", activity.createFlutterFragment().getDartEntrypointLibraryUri()); } @Test diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/KeyChannelResponderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/KeyChannelResponderTest.java index 8626d0e78db46..7fad24c620494 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/KeyChannelResponderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/KeyChannelResponderTest.java @@ -53,6 +53,6 @@ public void primaryResponderTest() { (canHandleEvent) -> { completionCallbackInvocationCounter[0]++; }); - assertEquals(completionCallbackInvocationCounter[0], 1); + assertEquals(1, completionCallbackInvocationCounter[0]); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index f0adf4465b1bf..19a35a973fb11 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -140,10 +141,10 @@ public void itCanBeRetrievedByHandle() { assertEquals(flutterEngine1, FlutterEngine.engineForId(1)); assertEquals(flutterEngine2, FlutterEngine.engineForId(2)); flutterEngine1.destroy(); - assertEquals(null, FlutterEngine.engineForId(1)); + assertNull(FlutterEngine.engineForId(1)); assertEquals(flutterEngine2, FlutterEngine.engineForId(2)); flutterEngine2.destroy(); - assertEquals(null, FlutterEngine.engineForId(2)); + assertNull(FlutterEngine.engineForId(2)); } // Helps show the root cause of MissingPluginException type errors like diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java index bac8a61a49413..216d48cac0cf2 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java @@ -83,7 +83,7 @@ public void itHasReasonableDefaultsWhenFlutterLoaderIsInitialized() { FlutterInjector.setInstance( new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); DartEntrypoint entrypoint = DartEntrypoint.createDefault(); - assertEquals(entrypoint.pathToBundle, "my/custom/path"); - assertEquals(entrypoint.dartEntrypointFunctionName, "main"); + assertEquals("my/custom/path", entrypoint.pathToBundle); + assertEquals("main", entrypoint.dartEntrypointFunctionName); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java index c5ed3e7395e6f..d9b334c57f872 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java @@ -342,7 +342,7 @@ public void testSerialTaskQueue() throws InterruptedException { latch.await(); assertEquals(count, ints.size()); for (int i = 0; i < count - 1; ++i) { - assertEquals((int) ints.get(i), (int) (ints.get(i + 1)) - 1); + assertEquals((int) ints.get(i), ints.get(i + 1) - 1); } } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java index 20c64ea281d7b..bc983db113f7b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java @@ -110,18 +110,18 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], soTestFilename); + assertEquals(soTestFilename, jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(soTestPath)); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 123); - assertEquals(jni.assetBundlePath, "flutter_assets"); + assertEquals(2, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); + assertEquals("flutter_assets", jni.assetBundlePath); } @Test @@ -141,18 +141,18 @@ public void downloadCallsJNIFunctionsWithFilenameFromManifest() throws NameNotFo TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], soTestFilename); + assertEquals(soTestFilename, jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(soTestPath)); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 123); - assertEquals(jni.assetBundlePath, "custom_assets"); + assertEquals(2, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); + assertEquals("custom_assets", jni.assetBundlePath); } @Test @@ -173,18 +173,18 @@ public void downloadCallsJNIFunctionsWithSharedLibraryNameFromManifest() TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], soTestFilename); + assertEquals(soTestFilename, jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(soTestPath)); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 123); - assertEquals(jni.assetBundlePath, "custom_assets"); + assertEquals(2, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); + assertEquals("custom_assets", jni.assetBundlePath); } @Test @@ -205,17 +205,17 @@ public void manifestMappingHandlesBaseModuleEmptyString() throws NameNotFoundExc PlayStoreDeferredComponentManager playStoreManager = new PlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(3, null); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 0); // no assets to load for base - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(0, jni.updateAssetManagerCalled); // no assets to load for base + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], soTestFilename); + assertEquals(soTestFilename, jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(soTestPath)); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 3); + assertEquals(2, jni.searchPaths.length); + assertEquals(3, jni.loadingUnitId); } @Test @@ -229,17 +229,17 @@ public void searchPathsAddsApks() throws NameNotFoundException { new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], "libapp.so-123.part.so"); + assertEquals("libapp.so-123.part.so", jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(apkTestPath + "!lib/armeabi-v7a/libapp.so-123.part.so")); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 123); + assertEquals(2, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); } @Test @@ -253,17 +253,17 @@ public void searchPathsSearchesSplitConfig() throws NameNotFoundException { new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], "libapp.so-123.part.so"); + assertEquals("libapp.so-123.part.so", jni.searchPaths[0]); assertTrue(jni.searchPaths[1].endsWith(apkTestPath + "!lib/armeabi-v7a/libapp.so-123.part.so")); - assertEquals(jni.searchPaths.length, 2); - assertEquals(jni.loadingUnitId, 123); + assertEquals(2, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); } @Test @@ -277,16 +277,16 @@ public void invalidSearchPathsAreIgnored() throws NameNotFoundException { new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); - assertEquals(jni.searchPaths[0], "libapp.so-123.part.so"); - assertEquals(jni.searchPaths.length, 1); - assertEquals(jni.loadingUnitId, 123); + assertEquals("libapp.so-123.part.so", jni.searchPaths[0]); + assertEquals(1, jni.searchPaths.length); + assertEquals(123, jni.loadingUnitId); } @Test @@ -301,12 +301,12 @@ public void assetManagerUpdateInvoked() throws NameNotFoundException { new TestPlayStoreDeferredComponentManager(spyContext, jni); jni.setDeferredComponentManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); + assertEquals(0, jni.loadingUnitId); playStoreManager.installDeferredComponent(123, "TestModuleName"); - assertEquals(jni.loadDartDeferredLibraryCalled, 1); - assertEquals(jni.updateAssetManagerCalled, 1); - assertEquals(jni.deferredComponentInstallFailureCalled, 0); + assertEquals(1, jni.loadDartDeferredLibraryCalled); + assertEquals(1, jni.updateAssetManagerCalled); + assertEquals(0, jni.deferredComponentInstallFailureCalled); assertEquals(jni.assetManager, assetManager); } @@ -318,7 +318,7 @@ public void stateGetterReturnsUnknowByDefault() throws NameNotFoundException { doReturn(null).when(spyContext).getAssets(); TestPlayStoreDeferredComponentManager playStoreManager = new TestPlayStoreDeferredComponentManager(spyContext, jni); - assertEquals(playStoreManager.getDeferredComponentInstallState(-1, "invalidName"), "unknown"); + assertEquals("unknown", playStoreManager.getDeferredComponentInstallState(-1, "invalidName")); } @Test diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 9be4c08bd0228..fabceff7c3e79 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -780,8 +780,8 @@ public void ImageReaderSurfaceProducerClampsWidthAndHeightTo1() { TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer(); // Default values. - assertEquals(producer.getWidth(), 1); - assertEquals(producer.getHeight(), 1); + assertEquals(1, producer.getWidth()); + assertEquals(1, producer.getHeight()); // Try setting width and height to 0. producer.setSize(0, 0); @@ -790,8 +790,8 @@ public void ImageReaderSurfaceProducerClampsWidthAndHeightTo1() { assertNotNull(producer.getSurface()); // Expect clamp to 1. - assertEquals(producer.getWidth(), 1); - assertEquals(producer.getHeight(), 1); + assertEquals(1, producer.getWidth()); + assertEquals(1, producer.getHeight()); } @Test @@ -806,7 +806,7 @@ public void SurfaceTextureSurfaceProducerCreatesAConnectedTexture() { flutterRenderer.startRenderingToSurface(fakeSurface, false); // Verify behavior under test. - assertEquals(producer.id(), 0); + assertEquals(0, producer.id()); verify(fakeFlutterJNI, times(1)).registerTexture(eq(producer.id()), any()); } finally { FlutterRenderer.debugForceSurfaceProducerGlTextures = false; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducerTest.java index 6dc4e997c5538..365cb901ddc0f 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducerTest.java @@ -59,7 +59,7 @@ public void createsSurfaceTextureOfGivenSizeAndResizesWhenRequested() { canvas.drawARGB(255, 255, 0, 0); surface.unlockCanvasAndPost(canvas); shadowOf(Looper.getMainLooper()).idle(); - assertEquals(frames.get(), 1); + assertEquals(1, frames.get()); // Resize and redraw. producer.setSize(400, 800); @@ -67,7 +67,7 @@ public void createsSurfaceTextureOfGivenSizeAndResizesWhenRequested() { canvas.drawARGB(255, 255, 0, 0); surface.unlockCanvasAndPost(canvas); shadowOf(Looper.getMainLooper()).idle(); - assertEquals(frames.get(), 2); + assertEquals(2, frames.get()); // Done. fakeJNI.detachFromNativeAndReleaseResources(); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java index 859b024b888b6..5897bdbf31fed 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java @@ -4,7 +4,6 @@ package io.flutter.embedding.engine.renderer; -import static junit.framework.TestCase.*; import static org.mockito.Mockito.*; import android.graphics.SurfaceTexture; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java index e5a5d6c3ed2a5..3c6834179fe8b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java @@ -66,7 +66,7 @@ public void platformChannel_shareInvokeMessage() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); fakePlatformChannel.parsingMethodCallHandler.onMethodCall(methodCall, mockResult); - assertEquals(valueCapture.getValue(), expectedContent); + assertEquals(expectedContent, valueCapture.getValue()); verify(mockResult).success(null); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java index 7aefeb9a79fb8..015af071ccc82 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java @@ -6,6 +6,7 @@ import static io.flutter.Build.API_LEVELS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -86,7 +87,7 @@ public void itPushesNewData() throws JSONException { verify(result).success(expected); restorationChannel.setRestorationData(data); - assertEquals(restorationChannel.getRestorationData(), null); + assertNull(restorationChannel.getRestorationData()); ArgumentCaptor resultCapture = ArgumentCaptor.forClass(MethodChannel.Result.class); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMessageCodecTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMessageCodecTest.java index 3e000e3f6073e..051e9a38cf46c 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMessageCodecTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMessageCodecTest.java @@ -120,7 +120,7 @@ public void itEncodesCharSequences() { message.flip(); String value = (String) codec.decodeMessage(message); - assertEquals(value, "hello world"); + assertEquals("hello world", value); } private static class NotEncodable { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java index dfad648be6fbe..65369cb03e73b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/common/StandardMethodCodecTest.java @@ -103,12 +103,12 @@ public void encodeErrorEnvelopeWithStacktraceTest() { final Object message = StandardMessageCodec.INSTANCE.readValue(buffer); final Object details = StandardMessageCodec.INSTANCE.readValue(buffer); final Object stacktrace = StandardMessageCodec.INSTANCE.readValue(buffer); - assertEquals("code", (String) code); - assertEquals("foo", (String) message); + assertEquals("code", code); + assertEquals("foo", message); String stack = (String) details; assertTrue( stack.contains( "at io.flutter.plugin.common.StandardMethodCodecTest.encodeErrorEnvelopeWithStacktraceTest(StandardMethodCodecTest.java:")); - assertEquals("error stacktrace", (String) stacktrace); + assertEquals("error stacktrace", stacktrace); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index 0238d9eeb3cbb..d0d8838174d67 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -643,53 +643,53 @@ public void testSendKeyEvent_leftKeyMovesCaretLeftComplexEmoji() { // Normal Character didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 74); + assertEquals(74, Selection.getSelectionStart(editable)); // Non-Spacing Mark didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 73); + assertEquals(73, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 72); + assertEquals(72, Selection.getSelectionStart(editable)); // Keycap didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 69); + assertEquals(69, Selection.getSelectionStart(editable)); // Keycap with invalid base adaptor.setSelection(68, 68); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 66); + assertEquals(66, Selection.getSelectionStart(editable)); adaptor.setSelection(67, 67); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 66); + assertEquals(66, Selection.getSelectionStart(editable)); // Zero Width Joiner didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 55); + assertEquals(55, Selection.getSelectionStart(editable)); // Zero Width Joiner with invalid base didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 53); + assertEquals(53, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 52); + assertEquals(52, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 51); + assertEquals(51, Selection.getSelectionStart(editable)); // ----- Start Emoji Tag Sequence with invalid base testing ---- // Delete base tag adaptor.setSelection(39, 39); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 37); + assertEquals(37, Selection.getSelectionStart(editable)); // Delete the sequence adaptor.setSelection(49, 49); @@ -697,80 +697,80 @@ public void testSendKeyEvent_leftKeyMovesCaretLeftComplexEmoji() { didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); } - assertEquals(Selection.getSelectionStart(editable), 37); + assertEquals(37, Selection.getSelectionStart(editable)); // ----- End Emoji Tag Sequence with invalid base testing ---- // Emoji Tag Sequence didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 23); + assertEquals(23, Selection.getSelectionStart(editable)); // Variation Selector with invalid base adaptor.setSelection(22, 22); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 21); + assertEquals(21, Selection.getSelectionStart(editable)); adaptor.setSelection(22, 22); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 21); + assertEquals(21, Selection.getSelectionStart(editable)); // Variation Selector didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 19); + assertEquals(19, Selection.getSelectionStart(editable)); // Emoji Modifier didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 16); + assertEquals(16, Selection.getSelectionStart(editable)); // Emoji Modifier with invalid base adaptor.setSelection(14, 14); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 13); + assertEquals(13, Selection.getSelectionStart(editable)); adaptor.setSelection(14, 14); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 13); + assertEquals(13, Selection.getSelectionStart(editable)); // Line Feed adaptor.setSelection(12, 12); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 11); + assertEquals(11, Selection.getSelectionStart(editable)); // Carriage Return adaptor.setSelection(12, 12); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 11); + assertEquals(11, Selection.getSelectionStart(editable)); // Carriage Return and Line Feed didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 9); + assertEquals(9, Selection.getSelectionStart(editable)); // Regional Indicator Symbol odd didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 7); + assertEquals(7, Selection.getSelectionStart(editable)); // Regional Indicator Symbol even didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 3); + assertEquals(3, Selection.getSelectionStart(editable)); // Simple Emoji didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 1); + assertEquals(1, Selection.getSelectionStart(editable)); // First CodePoint didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 0); + assertEquals(0, Selection.getSelectionStart(editable)); } @Test @@ -834,26 +834,26 @@ public void testSendKeyEvent_rightKeyMovesCaretRightComplexRegion() { // The cursor moves over two region indicators at a time. didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 4); + assertEquals(4, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 8); + assertEquals(8, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 12); + assertEquals(12, Selection.getSelectionStart(editable)); // When there is only one region indicator left with no pair, the cursor // moves over that single region indicator. didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 14); + assertEquals(14, Selection.getSelectionStart(editable)); // If the cursor is placed in the middle of a region indicator pair, it // moves over only the second half of the pair. adaptor.setSelection(6, 6); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 8); + assertEquals(8, Selection.getSelectionStart(editable)); } @Test @@ -868,65 +868,65 @@ public void testSendKeyEvent_rightKeyMovesCaretRightComplexEmoji() { // First CodePoint didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 1); + assertEquals(1, Selection.getSelectionStart(editable)); // Simple Emoji didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 3); + assertEquals(3, Selection.getSelectionStart(editable)); // Regional Indicator Symbol even didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 7); + assertEquals(7, Selection.getSelectionStart(editable)); // Regional Indicator Symbol odd didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 9); + assertEquals(9, Selection.getSelectionStart(editable)); // Carriage Return didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 10); + assertEquals(10, Selection.getSelectionStart(editable)); // Line Feed and Carriage Return didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 12); + assertEquals(12, Selection.getSelectionStart(editable)); // Line Feed didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 13); + assertEquals(13, Selection.getSelectionStart(editable)); // Modified Emoji didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 16); + assertEquals(16, Selection.getSelectionStart(editable)); // Emoji Modifier adaptor.setSelection(14, 14); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 16); + assertEquals(16, Selection.getSelectionStart(editable)); // Emoji Modifier with invalid base adaptor.setSelection(18, 18); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 19); + assertEquals(19, Selection.getSelectionStart(editable)); // Variation Selector didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 21); + assertEquals(21, Selection.getSelectionStart(editable)); // Variation Selector with invalid base adaptor.setSelection(22, 22); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 23); + assertEquals(23, Selection.getSelectionStart(editable)); // Emoji Tag Sequence for (int i = 0; i < 7; i++) { @@ -934,7 +934,7 @@ public void testSendKeyEvent_rightKeyMovesCaretRightComplexEmoji() { assertTrue(didConsume); assertEquals(Selection.getSelectionStart(editable), 25 + 2 * i); } - assertEquals(Selection.getSelectionStart(editable), 37); + assertEquals(37, Selection.getSelectionStart(editable)); // ----- Start Emoji Tag Sequence with invalid base testing ---- // Pass the sequence @@ -944,51 +944,51 @@ public void testSendKeyEvent_rightKeyMovesCaretRightComplexEmoji() { assertTrue(didConsume); assertEquals(Selection.getSelectionStart(editable), 41 + 2 * i); } - assertEquals(Selection.getSelectionStart(editable), 51); + assertEquals(51, Selection.getSelectionStart(editable)); // ----- End Emoji Tag Sequence with invalid base testing ---- // Zero Width Joiner with invalid base didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 52); + assertEquals(52, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 53); + assertEquals(53, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 55); + assertEquals(55, Selection.getSelectionStart(editable)); // Zero Width Joiner didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 66); + assertEquals(66, Selection.getSelectionStart(editable)); // Keycap with invalid base adaptor.setSelection(67, 67); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 68); + assertEquals(68, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 69); + assertEquals(69, Selection.getSelectionStart(editable)); // Keycap didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 72); + assertEquals(72, Selection.getSelectionStart(editable)); // Non-Spacing Mark didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 73); + assertEquals(73, Selection.getSelectionStart(editable)); didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 74); + assertEquals(74, Selection.getSelectionStart(editable)); // Normal Character didConsume = adaptor.handleKeyEvent(downKeyDown); assertTrue(didConsume); - assertEquals(Selection.getSelectionStart(editable), 75); + assertEquals(75, Selection.getSelectionStart(editable)); } @Test @@ -1061,26 +1061,26 @@ public void testSendKeyEvent_MovementKeysAreNopWhenNoSelection() { KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN); boolean didConsume = adaptor.handleKeyEvent(keyEvent); assertFalse(didConsume); - assertEquals(Selection.getSelectionStart(editable), -1); - assertEquals(Selection.getSelectionEnd(editable), -1); + assertEquals(-1, Selection.getSelectionStart(editable)); + assertEquals(-1, Selection.getSelectionEnd(editable)); keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP); didConsume = adaptor.handleKeyEvent(keyEvent); assertFalse(didConsume); - assertEquals(Selection.getSelectionStart(editable), -1); - assertEquals(Selection.getSelectionEnd(editable), -1); + assertEquals(-1, Selection.getSelectionStart(editable)); + assertEquals(-1, Selection.getSelectionEnd(editable)); keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT); didConsume = adaptor.handleKeyEvent(keyEvent); assertFalse(didConsume); - assertEquals(Selection.getSelectionStart(editable), -1); - assertEquals(Selection.getSelectionEnd(editable), -1); + assertEquals(-1, Selection.getSelectionStart(editable)); + assertEquals(-1, Selection.getSelectionEnd(editable)); keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); didConsume = adaptor.handleKeyEvent(keyEvent); assertFalse(didConsume); - assertEquals(Selection.getSelectionStart(editable), -1); - assertEquals(Selection.getSelectionEnd(editable), -1); + assertEquals(-1, Selection.getSelectionStart(editable)); + assertEquals(-1, Selection.getSelectionEnd(editable)); } @Test @@ -1092,9 +1092,9 @@ public void testMethod_getExtractedText() { ExtractedText extractedText = adaptor.getExtractedText(null, 0); - assertEquals(extractedText.text, SAMPLE_TEXT); - assertEquals(extractedText.selectionStart, selStart); - assertEquals(extractedText.selectionEnd, selStart); + assertEquals(SAMPLE_TEXT, extractedText.text); + assertEquals(selStart, extractedText.selectionStart); + assertEquals(selStart, extractedText.selectionEnd); } @Test diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java index 9624c04cde494..767f9f5e2d5b8 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java @@ -58,7 +58,7 @@ public void mouseCursorPlugin_SetsSystemCursorOnRequest() throws JSONException { methodResult); verify(testView, times(1)).getSystemPointerIcon(PointerIcon.TYPE_TEXT); verify(testView, times(1)).setPointerIcon(any(PointerIcon.class)); - assertEquals(methodResult.result, Boolean.TRUE); + assertEquals(Boolean.TRUE, methodResult.result); }); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java index 34c5ec9f001d4..ea8e0b6b98e16 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformPluginTest.java @@ -854,9 +854,9 @@ public void startChoosenActivityWhenSharingText() { // The intent action created by the plugin and passed to Intent.createChooser should be // 'Intent.ACTION_SEND'. Intent sendToIntent = intentCaptor.getValue(); - assertEquals(sendToIntent.getAction(), Intent.ACTION_SEND); - assertEquals(sendToIntent.getType(), "text/plain"); - assertEquals(sendToIntent.getStringExtra(Intent.EXTRA_TEXT), expectedContent); + assertEquals(Intent.ACTION_SEND, sendToIntent.getAction()); + assertEquals("text/plain", sendToIntent.getType()); + assertEquals(expectedContent, sendToIntent.getStringExtra(Intent.EXTRA_TEXT)); } @Config(sdk = API_LEVELS.API_29) diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java index 11c3094f982e7..095e5fcef8cab 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -4,10 +4,8 @@ package io.flutter.plugin.platform; -import static android.view.View.OnFocusChangeListener; import static io.flutter.Build.API_LEVELS; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.mockito.Mockito.spy; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsController2Test.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsController2Test.java index 0b532a37dc261..7b946bce66753 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsController2Test.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsController2Test.java @@ -366,7 +366,7 @@ public void createPlatformViewMessage_throwsIfViewIsNull() { // Simulate create call from the framework. createPlatformView(jni, PlatformViewsController2, platformViewId, "testType"); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertFalse(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId)); } @@ -392,7 +392,7 @@ public void createHybridPlatformViewMessage_throwsIfViewIsNull() { // Simulate create call from the framework. createPlatformView(jni, PlatformViewsController2, platformViewId, "testType"); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertFalse(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId)); } @@ -421,7 +421,7 @@ public void setPlatformViewDirection_throwIfPlatformViewNotFound() { // Simulate create call from the framework. createPlatformView(jni, PlatformViewsController2, platformViewId, "testType"); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); // Simulate set direction call from the framework. setLayoutDirection(jni, PlatformViewsController2, platformViewId, 1); @@ -430,7 +430,7 @@ public void setPlatformViewDirection_throwIfPlatformViewNotFound() { // The limit value of reply message will be equal to 2 if the layout direction is set // successfully, otherwise it will be much more than 2 due to the reply message contains // an error message wrapped with exception detail information. - assertEquals(ShadowFlutterJNI.getResponses().get(0).limit(), 2); + assertEquals(2, ShadowFlutterJNI.getResponses().get(0).limit()); } @Test diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 528216dea2b2a..bdde95c1baec0 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -256,8 +256,8 @@ public void itCancelsOldPresentationOnResize() { fakeVdController1.resize(10, 10, null); - assertEquals(fakeVdController1.presentation != presentation, true); - assertEquals(presentation.isShowing(), false); + assertNotSame(fakeVdController1.presentation, presentation); + assertFalse(presentation.isShowing()); } @Test @@ -1020,8 +1020,8 @@ public void createPlatformViewMessage_setsAndroidViewSize() { verify(androidView, times(2)).setLayoutParams(layoutParamsCaptor.capture()); List capturedLayoutParams = layoutParamsCaptor.getAllValues(); - assertEquals(capturedLayoutParams.get(0).width, 1); - assertEquals(capturedLayoutParams.get(0).height, 1); + assertEquals(1, capturedLayoutParams.get(0).width); + assertEquals(1, capturedLayoutParams.get(0).height); } @Test @@ -1073,7 +1073,7 @@ public void createPlatformViewMessage_throwsIfViewIsNull() { // Simulate create call from the framework. createPlatformView( jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertFalse(platformViewsController.initializePlatformViewIfNeeded(platformViewId)); } @@ -1098,7 +1098,7 @@ public void createHybridPlatformViewMessage_throwsIfViewIsNull() { // Simulate create call from the framework. createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertFalse(platformViewsController.initializePlatformViewIfNeeded(platformViewId)); } @@ -1192,7 +1192,7 @@ public void createPlatformViewMessage_throwsIfViewHasParent() { // Simulate create call from the framework. createPlatformView( jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertThrows( IllegalStateException.class, @@ -1222,7 +1222,7 @@ public void createHybridPlatformViewMessage_throwsIfViewHasParent() { // Simulate create call from the framework. createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); assertThrows( IllegalStateException.class, @@ -1253,7 +1253,7 @@ public void setPlatformViewDirection_throwIfPlatformViewNotFound() { // Simulate create call from the framework. createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); - assertEquals(ShadowFlutterJNI.getResponses().size(), 1); + assertEquals(1, ShadowFlutterJNI.getResponses().size()); // Simulate set direction call from the framework. setLayoutDirection(jni, platformViewsController, platformViewId, 1); @@ -1262,7 +1262,7 @@ public void setPlatformViewDirection_throwIfPlatformViewNotFound() { // The limit value of reply message will be equal to 2 if the layout direction is set // successfully, otherwise it will be much more than 2 due to the reply message contains // an error message wrapped with exception detail information. - assertEquals(ShadowFlutterJNI.getResponses().get(0).limit(), 2); + assertEquals(2, ShadowFlutterJNI.getResponses().get(0).limit()); } @Test @@ -1299,8 +1299,8 @@ public void resizeAndroidView() { ArgumentCaptor.forClass(FrameLayout.LayoutParams.class); verify(androidView, times(1)).setLayoutParams(layoutParamsCaptor.capture()); - assertEquals(layoutParamsCaptor.getValue().width, 10); - assertEquals(layoutParamsCaptor.getValue().height, 20); + assertEquals(10, layoutParamsCaptor.getValue().width); + assertEquals(20, layoutParamsCaptor.getValue().height); } @Test @@ -1512,7 +1512,7 @@ public void onEndFrame_removesPlatformViewParent() { // Simulate create call from the framework. createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); assertTrue(platformViewsController.initializePlatformViewIfNeeded(platformViewId)); - assertEquals(flutterView.getChildCount(), 2); + assertEquals(2, flutterView.getChildCount()); // Simulate first frame from the framework. jni.onFirstFrame(); @@ -1521,7 +1521,7 @@ public void onEndFrame_removesPlatformViewParent() { // Simulate dispose call from the framework. disposePlatformView(jni, platformViewsController, platformViewId); - assertEquals(flutterView.getChildCount(), 1); + assertEquals(1, flutterView.getChildCount()); } @Test @@ -1740,7 +1740,7 @@ public void convertPlatformViewRenderSurfaceAsDefault() { /* viewHeight=*/ 10, /* mutatorsStack=*/ new FlutterMutatorsStack()); - assertEquals(flutterView.getChildCount(), 3); + assertEquals(3, flutterView.getChildCount()); final View view = flutterView.getChildAt(1); assertTrue(view instanceof FlutterImageView); @@ -1790,8 +1790,8 @@ public void dontConverRenderSurfaceWhenFlagIsTrue() { /* viewHeight=*/ 10, /* mutatorsStack=*/ new FlutterMutatorsStack()); - assertEquals(flutterView.getChildCount(), 2); - assertTrue(!(flutterView.getChildAt(0) instanceof PlatformOverlayView)); + assertEquals(2, flutterView.getChildCount()); + assertFalse(flutterView.getChildAt(0) instanceof PlatformOverlayView); assertTrue(flutterView.getChildAt(1) instanceof FlutterMutatorView); // Simulate dispose call from the framework. @@ -1868,18 +1868,18 @@ public void revertImageViewAndRemoveImageView() { platformViewsController.onDisplayOverlaySurface(platformViewId, 0, 0, 10, 10); // This will contain three views: Background ImageView、PlatformView、Overlay ImageView - assertEquals(flutterView.getChildCount(), 3); + assertEquals(3, flutterView.getChildCount()); FlutterImageView imageView = flutterView.getCurrentImageSurface(); // Make sure the ImageView is inside the current FlutterView. - assertTrue(imageView != null); + assertNotNull(imageView); assertTrue(flutterView.indexOfChild(imageView) != -1); // Make sure the overlayView is inside the current FlutterView assertTrue(platformViewsController.getOverlayLayerViews().size() != 0); PlatformOverlayView overlayView = platformViewsController.getOverlayLayerViews().get(0); - assertTrue(overlayView != null); + assertNotNull(overlayView); assertTrue(flutterView.indexOfChild(overlayView) != -1); // Simulate in a new frame, there's no PlatformView, which is called @@ -1891,13 +1891,13 @@ public void revertImageViewAndRemoveImageView() { // Invoke all registered `FlutterUiDisplayListener` callback jni.onFirstFrame(); - assertEquals(null, flutterView.getCurrentImageSurface()); + assertNull(flutterView.getCurrentImageSurface()); // Make sure the background ImageVIew is not in the FlutterView - assertTrue(flutterView.indexOfChild(imageView) == -1); + assertEquals(-1, flutterView.indexOfChild(imageView)); // Make sure the overlay ImageVIew is not in the FlutterView - assertTrue(flutterView.indexOfChild(overlayView) == -1); + assertEquals(-1, flutterView.indexOfChild(overlayView)); } private static ByteBuffer encodeMethodCall(MethodCall call) { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java index dcb53d07a671f..d92631dd0b98b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java @@ -4,7 +4,6 @@ package io.flutter.plugin.platform; -import static io.flutter.Build.API_LEVELS; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTargetTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTargetTest.java index 1921c935b7a8c..5ebda32591386 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTargetTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTargetTest.java @@ -5,8 +5,6 @@ package io.flutter.plugin.platform; import static io.flutter.Build.API_LEVELS; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import android.annotation.TargetApi; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java index 8891e72cdbfcf..3976f9710891c 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java @@ -139,7 +139,7 @@ public void performProcessTextActionWithNoReturnedValue() { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mockActivity, times(1)).startActivityForResult(intentCaptor.capture(), anyInt()); Intent intent = intentCaptor.getValue(); - assertEquals(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT), textToBeProcessed); + assertEquals(textToBeProcessed, intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT)); // Simulate an Android activity answer which does not return a value. Intent resultIntent = new Intent(); @@ -188,7 +188,7 @@ public void performProcessTextActionWithReturnedValue() { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mockActivity, times(1)).startActivityForResult(intentCaptor.capture(), anyInt()); Intent intent = intentCaptor.getValue(); - assertEquals(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT), textToBeProcessed); + assertEquals(textToBeProcessed, intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT)); // Simulate an Android activity answer which returns a transformed text. String processedText = "Flutter!!!"; @@ -239,7 +239,7 @@ public void doNotCrashOnNonRelatedActivityResult() { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mockActivity, times(1)).startActivityForResult(intentCaptor.capture(), anyInt()); Intent intent = intentCaptor.getValue(); - assertEquals(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT), textToBeProcessed); + assertEquals(textToBeProcessed, intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT)); // Result to a request not sent by this plugin should be ignored. final int externalRequestCode = 42; From cde36126a6783527cd98b3d123910004430f329d Mon Sep 17 00:00:00 2001 From: pierre <6655696+guidezpl@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:20:17 +0000 Subject: [PATCH 003/147] Remove codecov badge from README (#189728) This is no longer updated with a workflow and a solution requires infra team work. Removing for now See https://github.com/flutter/flutter/issues/178913 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 94de84e49a7a1..9c228ee187312 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ [![Discord badge][]][Discord instructions] [![Twitter handle][]][Twitter badge] [![BlueSky badge][]][BlueSky handle] -[![codecov](https://codecov.io/gh/flutter/flutter/branch/master/graph/badge.svg?token=11yDrJU2M2)](https://codecov.io/gh/flutter/flutter) [![LFX Health Score](https://insights.linuxfoundation.org/api/badge/health-score?project=flutter)](https://insights.linuxfoundation.org/project/flutter) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5631/badge)](https://bestpractices.coreinfrastructure.org/projects/5631) [![SLSA 1](https://slsa.dev/images/gh-badge-level1.svg)](https://slsa.dev) From 5f8d728e02f4a54791debbcc510a764ce1f12cc9 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 22 Jul 2026 16:27:22 +0000 Subject: [PATCH 004/147] ci: Include root pubspec.yaml in pub cache key (#189826) The `pub package cache` step in composite-flutter-setup keys its cache on a hash of the pubspec.yaml files found under dev/, examples/, and packages/, but omits the root workspace pubspec.yaml. The root pubspec pins the versions of all transitive dependencies, so a pub roll that only touches the root, leaves the hash, and therefore the hash key, unchanged. This is exactly what happens on pub package rolls like flutter/flutter#189760. When that happens the cache still hits and restores a stale pubspec.lock over the freshly checked-out one. At that point, the following `flutter update-packages` then fails when run with `--enforce-lockfile`, since the lock file we just restored from cache no longer matches the pinned versions in the checked-out root pubspec.yaml, which breaks the Tree Analyze step tree-wide until the cache is manually invalidated. We now include the root pubspec.yaml in the hash so that a packages only roll changes the cache key, forcing a fresh package resolve rather than restoring a stale lock. Fixes: https://github.com/flutter/flutter/issues/189825 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: John "codefu" McDole --- .github/actions/composite-flutter-setup/action.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/actions/composite-flutter-setup/action.yml b/.github/actions/composite-flutter-setup/action.yml index 5e71b9efd1958..637ced9c3a60e 100644 --- a/.github/actions/composite-flutter-setup/action.yml +++ b/.github/actions/composite-flutter-setup/action.yml @@ -160,8 +160,9 @@ runs: shell: bash id: pub-deps-hash run: | - # Generate stable hash of pubspec.yaml files - find dev examples packages -name "pubspec.yaml" -print0 | sort -z | xargs -0 cat | sha256sum >> "$RUNNER_TEMP/pub_deps_sha" + # Generate a stable hash for github caching from all the pubspec.yaml + # files in the tree. Includes the root pubspec.yaml. + git ls-tree HEAD -- $(git ls-files ':(glob)**/pubspec.yaml') | git hash-object --stdin > "$RUNNER_TEMP/pub_deps_sha" echo "revision=$(cat "$RUNNER_TEMP/pub_deps_sha")" >> "$GITHUB_OUTPUT" - name: pub package cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae From 2a2a79d36624ad55d237579f7aae766d94eb2124 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:04:35 +0000 Subject: [PATCH 005/147] Clear cached directional focus history on a non-directional focus request (#187957) Fixes https://github.com/flutter/flutter/issues/85941 When a user traverses focus nodes directionally (e.g. using `up`, `down` arrow keys), we maintain a history stack of visited nodes (called `policyData`). This allows us to avoid [hysteresis](https://en.wikipedia.org/wiki/Hysteresis) when a user switches direction (i.e. switching from `down` to `up`) - meaning, the forward traversal path and the reversed traversal path should match. However, before this change, we were not detecting when a focus request occurred due to some outside event (e.g. a direct focus request from a tap on the screen). That means when a user switched direction, we would include the node they had tapped on in the history stack, and unexpectedly visit it. We now clear the stack (see method `propagateFocusRequest`) if a node is focused on outside of a directional navigation event. **Before:** before **With fix:** fixed ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/widgets/focus_traversal.dart | 113 ++++++++++++++--- .../test/widgets/focus_traversal_test.dart | 117 ++++++++++++++++-- 2 files changed, 204 insertions(+), 26 deletions(-) diff --git a/packages/flutter/lib/src/widgets/focus_traversal.dart b/packages/flutter/lib/src/widgets/focus_traversal.dart index 137aa549b36a3..e54eb5fbcd3b8 100644 --- a/packages/flutter/lib/src/widgets/focus_traversal.dart +++ b/packages/flutter/lib/src/widgets/focus_traversal.dart @@ -1123,6 +1123,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild, + _FocusTraversalGroupNode? groupNode, ) { final _DirectionalPolicyData? policyData = _policyData[nearestScope]; if (policyData != null && @@ -1154,7 +1155,7 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { case TraversalDirection.down: alignmentPolicy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd; } - requestFocusCallback(lastNode, alignmentPolicy: alignmentPolicy); + _requestFocus(lastNode, alignmentPolicy: alignmentPolicy, groupNode); return true; } @@ -1214,23 +1215,32 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { FocusNode node, FocusScopeNode nearestScope, TraversalDirection direction, + _FocusTraversalGroupNode? groupNode, ) { if (node is FocusScopeNode) { if (node.focusedChild != null) { - return _requestTraversalFocusInDirection(currentNode, node.focusedChild!, node, direction); + return _requestTraversalFocusInDirection( + currentNode, + node.focusedChild!, + node, + direction, + groupNode, + ); } final FocusNode firstNode = findFirstFocusInDirection(node, direction) ?? currentNode; switch (direction) { case TraversalDirection.up: case TraversalDirection.left: - requestFocusCallback( + _requestFocus( firstNode, + groupNode, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, ); case TraversalDirection.right: case TraversalDirection.down: - requestFocusCallback( + _requestFocus( firstNode, + groupNode, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, ); } @@ -1240,20 +1250,44 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { switch (direction) { case TraversalDirection.up: case TraversalDirection.left: - requestFocusCallback( + _requestFocus( node, + groupNode, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, ); case TraversalDirection.right: case TraversalDirection.down: - requestFocusCallback(node, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd); + _requestFocus( + node, + groupNode, + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); } return !nodeHadPrimaryFocus; } + void _requestFocus( + FocusNode node, + _FocusTraversalGroupNode? groupNode, { + ScrollPositionAlignmentPolicy? alignmentPolicy, + double? alignment, + Duration? duration, + Curve? curve, + }) { + groupNode?.lastRequestedFocus = node; + requestFocusCallback( + node, + alignmentPolicy: alignmentPolicy, + alignment: alignment, + duration: duration, + curve: curve, + ); + } + bool _onEdgeForDirection( FocusNode currentNode, FocusNode focusedChild, + _FocusTraversalGroupNode? groupNode, TraversalDirection direction, { FocusScopeNode? scope, }) { @@ -1275,7 +1309,13 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { direction, ); if (found == null) { - return _onEdgeForDirection(currentNode, focusedChild, direction, scope: nearestScope); + return _onEdgeForDirection( + currentNode, + focusedChild, + groupNode, + direction, + scope: nearestScope, + ); } } else { found = _findNextFocusInDirection( @@ -1296,7 +1336,13 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { return false; } if (found != null) { - return _requestTraversalFocusInDirection(currentNode, found, nearestScope, direction); + return _requestTraversalFocusInDirection( + currentNode, + found, + nearestScope, + direction, + groupNode, + ); } return false; } @@ -1321,27 +1367,31 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { @mustCallSuper @override bool inDirection(FocusNode currentNode, TraversalDirection direction) { + final _FocusTraversalGroupNode? groupNode = FocusTraversalGroup._getGroupNode(currentNode); final FocusScopeNode nearestScope = currentNode.nearestScope!; final FocusNode? focusedChild = nearestScope.focusedChild; if (focusedChild == null) { - final FocusNode firstFocus = findFirstFocusInDirection(currentNode, direction) ?? currentNode; + final FocusNode firstFocus = + findFirstFocusInDirection(currentNode, direction) ?? currentNode; switch (direction) { case TraversalDirection.up: case TraversalDirection.left: - requestFocusCallback( + _requestFocus( firstFocus, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + groupNode, ); case TraversalDirection.right: case TraversalDirection.down: - requestFocusCallback( + _requestFocus( firstFocus, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + groupNode, ); } return true; } - if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) { + if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild, groupNode)) { return true; } final FocusNode? found = _findNextFocusInDirection( @@ -1351,9 +1401,15 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy { ); if (found != null) { _pushPolicyData(direction, nearestScope, focusedChild); - return _requestTraversalFocusInDirection(currentNode, found, nearestScope, direction); + return _requestTraversalFocusInDirection( + currentNode, + found, + nearestScope, + direction, + groupNode, + ); } - return _onEdgeForDirection(currentNode, focusedChild, direction); + return _onEdgeForDirection(currentNode, focusedChild, groupNode, direction); } } @@ -2206,8 +2262,11 @@ class FocusTraversalGroup extends StatefulWidget { } // A special focus node subclass that only FocusTraversalGroup uses so that it -// can be used to cache the policy in the focus tree, and so that the traversal -// code can find groups in the focus tree. +// can be used to cache the following in the focus tree: +// - the focus traversal policy - this allows traversal code to find groups in +// the focus tree. +// - the last focused node - this allows the policy to be invalidated when a +// focus change was not triggered via a traversal request. class _FocusTraversalGroupNode extends FocusNode { _FocusTraversalGroupNode({super.debugLabel, required this.policy}) { if (kFlutterMemoryAllocationsEnabled) { @@ -2216,6 +2275,8 @@ class _FocusTraversalGroupNode extends FocusNode { } FocusTraversalPolicy policy; + + FocusNode? lastRequestedFocus; } class _FocusTraversalGroupState extends State { @@ -2232,11 +2293,13 @@ class _FocusTraversalGroupState extends State { @override void initState() { super.initState(); + FocusManager.instance.addListener(_handleFocusChanged); widget.onFocusNodeCreated?.call(focusNode); } @override void dispose() { + FocusManager.instance.removeListener(_handleFocusChanged); focusNode.dispose(); super.dispose(); } @@ -2262,6 +2325,24 @@ class _FocusTraversalGroupState extends State { child: widget.child, ); } + + void _handleFocusChanged() { + final FocusNode? primaryFocus = FocusManager.instance.primaryFocus; + final FocusNode? lastRequestedFocus = focusNode.lastRequestedFocus; + + if (lastRequestedFocus == null) { + return; + } + + if (primaryFocus != lastRequestedFocus) { + FocusScopeNode? scope = primaryFocus?.nearestScope; + while (scope != null) { + widget.policy.invalidateScopeData(scope); + scope = scope.enclosingScope; + } + focusNode.lastRequestedFocus = null; + } + } } /// An intent for use with the [RequestFocusAction], which supplies the diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index e16e5247082be..0d5697b8e571c 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -1805,7 +1805,7 @@ void main() { }); testWidgets('Directional focus avoids hysteresis.', (WidgetTester tester) async { - var focus = List.generate(6, (int _) => null); + List focus = _createFocusTracker(6); final nodes = List.generate( 6, (int index) => FocusNode(debugLabel: 'Node $index'), @@ -1858,7 +1858,7 @@ void main() { ); void clear() { - focus = List.generate(focus.length, (int _) => null); + focus = _createFocusTracker(focus.length); } final FocusNode scope = nodes[0].enclosingScope!; @@ -1922,12 +1922,106 @@ void main() { clear(); }); + // Regression test for https://github.com/flutter/flutter/issues/85941. + testWidgets('Directional focus history is cleared on explicit focus request', ( + WidgetTester tester, + ) async { + List focus = _createFocusTracker(5); + final nodes = List.generate( + 5, + (int index) => FocusNode(debugLabel: 'Node $index'), + ); + addTearDown(() { + for (final node in nodes) { + node.dispose(); + } + }); + + Widget makeFocus(int index) { + return Focus( + debugLabel: '[$index]', + focusNode: nodes[index], + onFocusChange: (bool isFocused) => focus[index] = isFocused, + child: const SizedBox(width: 100, height: 100), + ); + } + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: FocusTraversalGroup( + policy: WidgetOrderTraversalPolicy(), + child: FocusScope( + debugLabel: 'Scope', + child: Column( + children: [ + makeFocus(0), + makeFocus(1), + makeFocus(2), + makeFocus(3), + makeFocus(4), + ], + ), + ), + ), + ), + ); + + void clear() { + focus = _createFocusTracker(focus.length); + } + + final FocusNode scope = nodes[0].enclosingScope!; + nodes[0].requestFocus(); + await tester.pump(); + clear(); + + // Move down three times. + expect(scope.focusInDirection(TraversalDirection.down), isTrue); + await tester.pump(); + expect(focus, orderedEquals([false, true, null, null, null])); + clear(); + + expect(scope.focusInDirection(TraversalDirection.down), isTrue); + await tester.pump(); + expect(focus, orderedEquals([null, false, true, null, null])); + clear(); + + expect(scope.focusInDirection(TraversalDirection.down), isTrue); + await tester.pump(); + expect(focus, orderedEquals([null, null, false, true, null])); + clear(); + + // Shift focus externally back to [1]. + nodes[1].requestFocus(); + await tester.pump(); + expect(focus, orderedEquals([null, true, null, false, null])); + clear(); + + // Move down once. + expect(scope.focusInDirection(TraversalDirection.down), isTrue); + await tester.pump(); + expect(focus, orderedEquals([null, false, true, null, null])); + clear(); + + // Move up twice. + expect(scope.focusInDirection(TraversalDirection.up), isTrue); + await tester.pump(); + expect(focus, orderedEquals([null, true, false, null, null])); + clear(); + + expect(scope.focusInDirection(TraversalDirection.up), isTrue); + await tester.pump(); + expect(focus, orderedEquals([true, false, null, null, null])); + clear(); + }); + testWidgets('Directional prefers the closest node even on irregular grids', ( WidgetTester tester, ) async { const cols = 3; const rows = 3; - var focus = List.generate(rows * cols, (int _) => null); + List focus = _createFocusTracker(rows * cols); final nodes = List.generate( rows * cols, (int index) => FocusNode(debugLabel: 'Node $index'), @@ -1989,7 +2083,7 @@ void main() { ); void clear() { - focus = List.generate(focus.length, (int _) => null); + focus = _createFocusTracker(focus.length); } final FocusNode scope = nodes[0].enclosingScope!; @@ -2060,7 +2154,7 @@ void main() { WidgetTester tester, ) async { const rows = 4; - var focus = List.generate(rows, (int _) => null); + List focus = _createFocusTracker(rows); final nodes = List.generate( rows, (int index) => FocusNode(debugLabel: 'Node $index'), @@ -2114,7 +2208,7 @@ void main() { ); void clear() { - focus = List.generate(focus.length, (int _) => null); + focus = _createFocusTracker(focus.length); } final FocusNode scope = nodes[0].enclosingScope!; @@ -2151,7 +2245,7 @@ void main() { WidgetTester tester, ) async { const cols = 4; - var focus = List.generate(cols, (int _) => null); + List focus = _createFocusTracker(cols); final nodes = List.generate( cols, (int index) => FocusNode(debugLabel: 'Node $index'), @@ -2205,7 +2299,7 @@ void main() { ); void clear() { - focus = List.generate(focus.length, (int _) => null); + focus = _createFocusTracker(focus.length); } final FocusNode scope = nodes[0].enclosingScope!; @@ -3881,7 +3975,7 @@ void main() { }); testWidgets('Edge cases for inDirection', (WidgetTester tester) async { - var focus = List.generate(6, (int _) => null); + List focus = _createFocusTracker(6); final nodes = List.generate(6, (int index) => FocusNode(debugLabel: 'Node $index')); final childScope = FocusScopeNode(debugLabel: 'Child Scope'); addTearDown(() { @@ -3952,7 +4046,7 @@ void main() { await pumpApp(); void clear() { - focus = List.generate(focus.length, (int _) => null); + focus = _createFocusTracker(focus.length); } Future resetTo(int index) async { @@ -4148,3 +4242,6 @@ class SkipAllButFirstAndLastPolicy extends FocusTraversalPolicy ]; } } + +/// Creates a list of [length] to track focus changes during the test suite. +List _createFocusTracker(int length) => List.generate(length, (int index) => null); From ef87dc1ed83df7ed5ee5ba365715b8217dcd43f3 Mon Sep 17 00:00:00 2001 From: CodeDoctor Date: Wed, 22 Jul 2026 18:03:05 +0000 Subject: [PATCH 006/147] Fix null terminator in input truncates clipboard (#188652) Closes https://github.com/flutter/flutter/issues/162226. Sadly win32 doesn't allow `\0` characters inside the `CF_UNICODETEXT` clipboard. To prevent it, I replaced the null terminator with the `\uFFFD` (replacement character: https://www.compart.com/en/unicode/U+FFFD). I replaced the string type to a string view to also store the size on the way so it doesn't break on the way if there are null terminators. ~~Currently a draft since I don't have tests yet.~~ ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../fml/platform/win/wstring_conversion.cc | 4 +-- .../platform/windows/platform_handler.cc | 12 +++++-- .../shell/platform/windows/platform_handler.h | 2 +- .../windows/platform_handler_unittests.cc | 32 ++++++++++++++++++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/engine/src/flutter/fml/platform/win/wstring_conversion.cc b/engine/src/flutter/fml/platform/win/wstring_conversion.cc index ef87f60e89e6b..41e34862f868d 100644 --- a/engine/src/flutter/fml/platform/win/wstring_conversion.cc +++ b/engine/src/flutter/fml/platform/win/wstring_conversion.cc @@ -15,12 +15,12 @@ using WideStringConverter = std::string WideStringToUtf8(const std::wstring_view str) { WideStringConverter converter; - return converter.to_bytes(str.data()); + return converter.to_bytes(str.data(), str.data() + str.size()); } std::wstring Utf8ToWideString(const std::string_view str) { WideStringConverter converter; - return converter.from_bytes(str.data()); + return converter.from_bytes(str.data(), str.data() + str.size()); } std::u16string WideStringToUtf16(const std::wstring_view str) { diff --git a/engine/src/flutter/shell/platform/windows/platform_handler.cc b/engine/src/flutter/shell/platform/windows/platform_handler.cc index aa05bf3750be9..b817600ea654d 100644 --- a/engine/src/flutter/shell/platform/windows/platform_handler.cc +++ b/engine/src/flutter/shell/platform/windows/platform_handler.cc @@ -341,7 +341,7 @@ void PlatformHandler::GetHasStrings( } void PlatformHandler::SetPlainText( - const std::string& text, + std::string_view text, std::unique_ptr> result) { std::unique_ptr clipboard = scoped_clipboard_provider_(); @@ -353,7 +353,11 @@ void PlatformHandler::SetPlainText( result->Error(kClipboardError, "Unable to open clipboard", error_code); return; } - int set_result = clipboard->SetString(fml::Utf8ToWideString(text)); + std::wstring clipboard_text = fml::Utf8ToWideString(text); + // Windows clipboard strings are null-terminated, so an embedded null + // character causes other applications to paste only the text before it. + std::replace(clipboard_text.begin(), clipboard_text.end(), L'\0', L'\uFFFD'); + int set_result = clipboard->SetString(clipboard_text); if (set_result != kErrorSuccess) { rapidjson::Document error_code; error_code.SetInt(set_result); @@ -504,7 +508,9 @@ void PlatformHandler::HandleMethodCall( result->Error(kClipboardError, kUnknownClipboardFormatMessage); return; } - SetPlainText(itr->value.GetString(), std::move(result)); + SetPlainText( + std::string_view(itr->value.GetString(), itr->value.GetStringLength()), + std::move(result)); } else if (method.compare(kPlaySoundMethod) == 0) { // Only one string argument is expected. const rapidjson::Value& sound_type = method_call.arguments()[0]; diff --git a/engine/src/flutter/shell/platform/windows/platform_handler.h b/engine/src/flutter/shell/platform/windows/platform_handler.h index cab985cb61cc1..08763633dc03e 100644 --- a/engine/src/flutter/shell/platform/windows/platform_handler.h +++ b/engine/src/flutter/shell/platform/windows/platform_handler.h @@ -69,7 +69,7 @@ class PlatformHandler { // Sets the clipboard's plain text to |text|, and reports the result (either // an error, or null for success) to |result|. virtual void SetPlainText( - const std::string& text, + std::string_view text, std::unique_ptr> result); virtual void SystemSoundPlay( diff --git a/engine/src/flutter/shell/platform/windows/platform_handler_unittests.cc b/engine/src/flutter/shell/platform/windows/platform_handler_unittests.cc index 878245556a299..ab438f5a440cd 100644 --- a/engine/src/flutter/shell/platform/windows/platform_handler_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/platform_handler_unittests.cc @@ -38,6 +38,9 @@ static constexpr char kClipboardHasStringsFakeContentTypeMessage[] = "{\"method\":\"Clipboard.hasStrings\",\"args\":\"text/madeupcontenttype\"}"; static constexpr char kClipboardSetDataMessage[] = "{\"method\":\"Clipboard.setData\",\"args\":{\"text\":\"hello\"}}"; +static constexpr char kClipboardSetDataNullTerminatorMessage[] = + "{\"method\":\"Clipboard.setData\",\"args\":{\"text\":" + "\"hello\\u0000world\"}}"; static constexpr char kClipboardSetDataNullTextMessage[] = "{\"method\":\"Clipboard.setData\",\"args\":{\"text\":null}}"; static constexpr char kClipboardSetDataUnknownTypeMessage[] = @@ -82,7 +85,7 @@ class MockPlatformHandler : public PlatformHandler { (override)); MOCK_METHOD(void, SetPlainText, - (const std::string&, + (std::string_view, std::unique_ptr>), (override)); MOCK_METHOD(void, @@ -358,6 +361,33 @@ TEST_F(PlatformHandlerTest, ClipboardSetData) { EXPECT_EQ(result, "[null]"); } +// Regression test for https://github.com/flutter/flutter/issues/162226. +TEST_F(PlatformHandlerTest, ClipboardSetDataReplacesNullTerminators) { + UseHeadlessEngine(); + + TestBinaryMessenger messenger; + PlatformHandler platform_handler(&messenger, engine(), []() { + auto clipboard = std::make_unique(); + + EXPECT_CALL(*clipboard.get(), Open) + .Times(1) + .WillOnce(Return(kErrorSuccess)); + EXPECT_CALL(*clipboard.get(), SetString) + .Times(1) + .WillOnce([](std::wstring string) { + EXPECT_EQ(string, L"hello\uFFFDworld"); + return kErrorSuccess; + }); + + return clipboard; + }); + + std::string result = SimulatePlatformMessage( + &messenger, kClipboardSetDataNullTerminatorMessage); + + EXPECT_EQ(result, "[null]"); +} + // Regression test for: https://github.com/flutter/flutter/issues/121976 TEST_F(PlatformHandlerTest, ClipboardSetDataTextMustBeString) { UseHeadlessEngine(); From cb346205ac8670e47a2362863c2c342aba760bb4 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:31:23 +0000 Subject: [PATCH 007/147] Skip emulator.getEmulators test (#189879) Temporary workaround for https://github.com/flutter/flutter/issues/189876. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../commands.shard/hermetic/daemon_test.dart | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index 541d2272e8ab3..de97055357570 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -14,6 +14,7 @@ import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/commands/daemon.dart'; @@ -731,15 +732,20 @@ void main() { expect(response.data['error'], contains('coldBoot is not a bool')); }); - testUsingContext('emulator.getEmulators should respond with list', () async { - daemon = Daemon(daemonConnection, notifyingLogger: notifyingLogger); - daemonStreams.inputs.add( - DaemonMessage({'id': 0, 'method': 'emulator.getEmulators'}), - ); - final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent); - expect(response.data['id'], 0); - expect(response.data['result'], isList); - }); + testUsingContext( + 'emulator.getEmulators should respond with list', + () async { + daemon = Daemon(daemonConnection, notifyingLogger: notifyingLogger); + daemonStreams.inputs.add( + DaemonMessage({'id': 0, 'method': 'emulator.getEmulators'}), + ); + final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent); + expect(response.data['id'], 0); + expect(response.data['result'], isList); + }, + // TODO(vashworth): https://github.com/flutter/flutter/issues/189876 + skip: const LocalPlatform().isMacOS, + ); testUsingContext('daemon can send exposeUrl requests to the client', () async { const originalUrl = 'http://localhost:1234/'; From de177df56d2361fe6356b1f5ecb44936b5a86dd3 Mon Sep 17 00:00:00 2001 From: b-luk <97480502+b-luk@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:36:48 +0000 Subject: [PATCH 008/147] Fix `Rect::ExpandToMinTransformedSize` to return the input rectangle when no expansion is needed, and remove 1-pixel roundrect to rect simplification (#189808) Fixes https://github.com/flutter/flutter/issues/189807 See the issue for more context. Adds a unit test that fails without this change, and passes with this change. Verified the demo app in the issue renders properly with this change. From the issue: ## Root cause and fix [Rect::ExpandToMinTransformedSize](https://github.com/flutter/flutter/blob/1c8b3088238220102df66761685f3a4718b5aa15/engine/src/flutter/impeller/geometry/rect.h#L705) always constructs a new expanded rectangle from the input Rect from its center point and half-extents. If the input rectangle is already large enough and does not need expansion, this should return an identical rectangle. The returned rectangle is equality-checked against the input in [Canvas::DrawRoundRect](https://github.com/flutter/flutter/blob/1c8b3088238220102df66761685f3a4718b5aa15/engine/src/flutter/impeller/display_list/canvas.cc#L1129) to determine whether expansion happened. That code assumes that if the output does not equal the input, pixel-expansion happened, which means that the rounded rect had sub-pixel dimensions and can be safely drawn as a non-rounded rectangle. The issue is that for certain cases, particularly with non-integer sizes, ExpandToMinTransformedSize returns a different rectangle even if no expansion happened. This is due to small floating point imprecisions when constructing the new rectangle using the input rectangle's center and half-extents. To fix this, ExpandToMinTransformedSize should not construct a new rectangle unless expansion is actually needed. ## Removal of 1-pixel roundrect to rect simplification This PR also removes the 1-pixel roundrect to rect simplification in `Canvas::DrawRoundRect`. As described in https://github.com/flutter/flutter/issues/189807#issuecomment-5042858850, this optimization resulted in slightly different colors at the tips of thin roundrects. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/impeller/display_list/canvas.cc | 27 +++++++------------ engine/src/flutter/impeller/geometry/rect.h | 7 +++++ .../impeller/geometry/rect_unittests.cc | 12 +++++++-- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 529653f280988..02b6dd130c8dc 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1111,36 +1111,27 @@ void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) { if (renderer_.GetContext()->GetFlags().use_sdfs && IsCompatibleWithSDFRendering(paint) && radii.AreAllCornersCircular()) { + Color effective_color = paint.color; + Rect bounds = round_rect.GetBounds(); + // Expand rrect bounds to 1 pixel minimum dimensions if applicable. if (paint.style == Paint::Style::kFill && !GetCurrentTransform().HasPerspective2D()) { - Rect rrect_bounds = round_rect.GetBounds(); - auto [expanded, alpha_scaled_color] = - ExpandRectToPixelMinimum(rrect_bounds, paint.color, - GetCurrentTransform(), /*scale_alpha=*/true); + auto [expanded, alpha_scaled_color] = ExpandRectToPixelMinimum( + bounds, paint.color, GetCurrentTransform(), /*scale_alpha=*/true); if (expanded.IsEmpty()) { // RRect is invisible due to transform scaling or alpha scaling. return; } - // Pixel-minimum expansion is applicable if the expanded bounds is - // different from the original bounds. - if (expanded != rrect_bounds) { - // At a 1-pixel size, the rounded corners can be ignored. Draw a regular - // rect matching the expanded bounds. - auto params = UberSDFParameters::MakeRect( - /*color=*/alpha_scaled_color, - /*rect=*/expanded, - /*stroke=*/std::nullopt); - AddRenderSDFEntityToCurrentPass(paint, params); - return; - } + bounds = expanded; + effective_color = alpha_scaled_color; } auto params = UberSDFParameters::MakeRoundedRect( - /*color=*/paint.color, - /*rect=*/round_rect.GetBounds(), + /*color=*/effective_color, + /*rect=*/bounds, /*radii=*/radii, /*stroke=*/paint.style == Paint::Style::kStroke ? std::make_optional(paint.stroke) diff --git a/engine/src/flutter/impeller/geometry/rect.h b/engine/src/flutter/impeller/geometry/rect.h index 0f67b795e174e..084e4113a374a 100644 --- a/engine/src/flutter/impeller/geometry/rect.h +++ b/engine/src/flutter/impeller/geometry/rect.h @@ -719,6 +719,13 @@ struct TRect { return std::nullopt; } Size current_size = GetSize(); + + if (current_size.width >= min_local_size.width && + current_size.height >= min_local_size.height) { + // No expansion needed. + return *this; + } + Size expanded_size = current_size.Max(min_local_size); return MakeEllipseBounds(GetCenter(), Point(expanded_size.width * 0.5f, expanded_size.height * 0.5f)); diff --git a/engine/src/flutter/impeller/geometry/rect_unittests.cc b/engine/src/flutter/impeller/geometry/rect_unittests.cc index df6d677241428..b8362c24eaf97 100644 --- a/engine/src/flutter/impeller/geometry/rect_unittests.cc +++ b/engine/src/flutter/impeller/geometry/rect_unittests.cc @@ -1444,13 +1444,21 @@ TEST(RectTest, RectExpandToMinTransformedSizeNullForScaledToZero) { std::nullopt); } -TEST(RectTest, - RectExpandToMinTransformedSizeRectReturnsUnmodifiedWhenLargeEnough) { +TEST(RectTest, RectExpandToMinTransformedSizeReturnsUnmodifiedWhenLargeEnough) { Rect rect = Rect::MakeXYWH(0, 0, 10, 20); auto transform = Matrix::MakeScale(Vector3(0.5f, 0.5f)); EXPECT_EQ(rect.ExpandToMinTransformedSize({1.0f, 1.0f}, transform), rect); } +TEST( + RectTest, + RectExpandToMinTransformedSizeReturnsUnmodifiedWhenLargeEnoughWithFractionalSize) { + // Regression test for https://github.com/flutter/flutter/issues/189807. + Rect rect = Rect::MakeXYWH(100.0f, 50.0f, 64.2f, 40.0f); + auto transform = Matrix(); + EXPECT_EQ(rect.ExpandToMinTransformedSize({1.0f, 1.0f}, transform), rect); +} + TEST(RectTest, RectExpandToMinTransformedSizeRectWithIdentityTransform) { Size size = Size(2.0f, 2.0f); Rect rect = Rect::MakeEllipseBounds(Point(), size * 0.5f); From 165a6a29ed4c19c03caa2302a3561c973666ef98 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 22 Jul 2026 21:40:09 +0000 Subject: [PATCH 009/147] [iOS] Mark DisplayLinkManager.shared and init() @MainActor (#189815) Now that DisplayLinkManager is injected in nearly all use-cases and the `.shared` singleton is used at exactly 3 remaining call-sites, marks `.shared` and `init()` as `@MainActor` now that it's safe to do so. `DisplayLinkManager.init()` has always required betin called on the main thread since it reads `UIScreen.main`/`Bundle.main`, but that was only enforced by a runtime assert. which compiles out in most release configruations. The Swift compiler can now enforce a static, checkable contract instead. Actor-isolation checking is enforced in Swift 5.5+ regardless of strict-concurrency level or language mode, so this takes should take effect immediately given our current Swift 5 language mode/default concurrency checking not gated behind a migration. I've left the manual `assert(Thread.isMainThread, ...)` in place for now given the existing Obj-C use in `FlutterViewController` and `FlutterMetalLayer`. If we do eventually remove it, we should ensure that the runtime itself is enforcing this runtime behaviour first -- this is dependent on SDK version. I've also left the internal testing initializer non-isolated: it doesn't touch `UIScreen`/`Bundle.main`, so it has no main-thread requirement, and leaving it alone keeps the two tests that use it compiling unchanged. `DisplayLinkManagerTest.swift`'s `testSharedInstanceReturnsAValidValue` and `testDisplayConfigurationNotificationsAreHandledWithoutCrashing` both call `.shared` directly, so we needed to mark the test class `@MainActor`. XCTest already runs these on the main thread, so this is a no-op but makes the compiler (which doesn't know about XCTest's implementation) happy. No semantic changes to tests because there's no semantic change to the logic, and this is enforced by the complier itself. Issue: https://github.com/flutter/flutter/issues/175879 Issue: https://github.com/flutter/flutter/issues/181684 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../darwin/ios/framework/Source/DisplayLinkManager.swift | 5 ++++- .../darwin/ios/framework/Source/DisplayLinkManagerTest.swift | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManager.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManager.swift index afafd5e63ea83..15d8e484e35ee 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManager.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManager.swift @@ -36,7 +36,9 @@ public final class DisplayLinkManager: NSObject, @unchecked Sendable { /// The shared DisplayLinkManager. /// /// The first access performs a one-time read of `UIScreen.main`, and must happen on the main - /// thread; this is enforced by an assertion in `init()`. + /// thread; `@MainActor` isolation enforces this for Swift callers at compile time. Objective-C + /// callers remain responsible for calling from the main thread themselves. + @MainActor @objc public static let shared = DisplayLinkManager() @@ -90,6 +92,7 @@ public final class DisplayLinkManager: NSObject, @unchecked Sendable { /// /// Queries the system plist and main screen properties on the main thread, then starts observing /// for changes that can affect the cached refresh rate. + @MainActor private override init() { assert( Thread.isMainThread, diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift index ad12b260b56df..4f693b0b52ac8 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift @@ -6,6 +6,7 @@ import XCTest @testable import InternalFlutterSwift +@MainActor class DisplayLinkManagerTest: XCTestCase { func testDisplayLinkManagerCanBeInstantiatedWithMockValues() { From b322c111ab15d127dc447af44caab97f2a3ea72d Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 22 Jul 2026 22:05:03 +0000 Subject: [PATCH 010/147] [ios,macos] Add Swift Sourcekit LSP support (#189761) This adds a post-processing pass to `complie_commands.json` that synthesizes the Swift entries that can be understood by SourceKit LSP and wires up the editor config needed to pick them up. GN's `--export-compile-commands` only understands the built-in `cc`/`cxx`/`objc`/`objcxx` tool types, and doesn't yet have built-in support for injecting `swiftc` lines, so our custom `swift` tool (which invokes `swiftc.py`) never gets an entry there, meaning Swift code in the iOS and macOS embedders isn't indexed by SourceKit LSP and other tooling that reads that file. Since GN's own compdb export omits the swift targets, `_postGn()` now shells out (once) to `ninja -t compdb` for the full compilation database (and falls back to the existing `compile_commands.json` on disk if that fails), then runs it through `expandSwiftcCommands`, which turns each `swiftc.py`-wrapped entry into one or more native `swiftc` invocations, one per compiled Swift file, with relative paths resolved to absolute against the entry's `directory`. We drop `-isystem` and `-Dkey=value` flags since that's what `swiftc.py` does; swiftc only accepts boolean -D defines not key=value flags. This scans the raw ninja output directly rather than decoding all of `compile_commands.json`, which can run past 20MB and is too slow to parse wholesale in Dart; only the small handful of matched `swiftc.py` entries get `jsonDecode`d -- fewer than 10 lines. This also wires up `.sourcekit-lsp/config.json` and the `swift.sourcekit-lsp.supported-languages` setting in `engine.code-workspace`/`engine-workspace.yaml` so that VS Code's Swift extension correctly picks up the generated compdb. Fixes https://github.com/flutter/flutter/issues/185741 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- engine/src/flutter/.sourcekit-lsp/config.json | 7 + engine/src/flutter/engine.code-workspace | 3 + .../test/header_filter_regex_test.dart | 1 + .../test/commands/build_command_test.dart | 35 +- .../test/commands/run_command_test.dart | 2 +- .../lib/src/build_config_runner.dart | 49 ++- .../lib/src/update_compdb.dart | 335 ++++++++++++++++++ .../test/update_compdb_test.dart | 218 ++++++++++++ .../vscode_workspace/engine-workspace.yaml | 2 + 9 files changed, 634 insertions(+), 18 deletions(-) create mode 100644 engine/src/flutter/.sourcekit-lsp/config.json diff --git a/engine/src/flutter/.sourcekit-lsp/config.json b/engine/src/flutter/.sourcekit-lsp/config.json new file mode 100644 index 0000000000000..e7d11af0287e1 --- /dev/null +++ b/engine/src/flutter/.sourcekit-lsp/config.json @@ -0,0 +1,7 @@ +{ + "compilationDatabase": { + "searchPaths": [ + "../" + ] + } +} diff --git a/engine/src/flutter/engine.code-workspace b/engine/src/flutter/engine.code-workspace index b3c6a99ff0faf..f26720113c4b2 100644 --- a/engine/src/flutter/engine.code-workspace +++ b/engine/src/flutter/engine.code-workspace @@ -137,6 +137,9 @@ ], "dotnet.defaultSolution": "disable", "dart.showTodos": false, + "swift.sourcekit-lsp.supported-languages": [ + "swift" + ], "testMate.cpp.test.advancedExecutables": [ { "name": "impeller_unittests_arm64", diff --git a/engine/src/flutter/tools/clang_tidy/test/header_filter_regex_test.dart b/engine/src/flutter/tools/clang_tidy/test/header_filter_regex_test.dart index 0d97ec4e82ca6..3abde0f901e48 100644 --- a/engine/src/flutter/tools/clang_tidy/test/header_filter_regex_test.dart +++ b/engine/src/flutter/tools/clang_tidy/test/header_filter_regex_test.dart @@ -40,6 +40,7 @@ void main() { '.git', '.gemini', '.github', + '.sourcekit-lsp', '.vscode', 'build_overrides', 'buildtools', diff --git a/engine/src/flutter/tools/engine_tool/test/commands/build_command_test.dart b/engine/src/flutter/tools/engine_tool/test/commands/build_command_test.dart index 99d15ef38ca7c..bba1b8900e0e7 100644 --- a/engine/src/flutter/tools/engine_tool/test/commands/build_command_test.dart +++ b/engine/src/flutter/tools/engine_tool/test/commands/build_command_test.dart @@ -54,7 +54,10 @@ void main() { printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n')); expect(result, equals(0)); expect(testEnv.processHistory.length, greaterThanOrEqualTo(2)); - expect(testEnv.processHistory[1].command[0], contains('ninja')); + final ExecutedProcess ninja = testEnv.processHistory.firstWhere( + (p) => p.command.contains('-C'), + ); + expect(ninja.command[0], contains('ninja')); }); test('build command invokes generator', () async { @@ -127,7 +130,12 @@ void main() { printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n')); expect(result, equals(0)); - final [ExecutedProcess gnCall, ExecutedProcess reclientCall, ..._] = testEnv.processHistory; + final ExecutedProcess gnCall = testEnv.processHistory.firstWhere( + (p) => p.command.first.endsWith('tools/gn'), + ); + final ExecutedProcess reclientCall = testEnv.processHistory.firstWhere( + (p) => p.command.first.endsWith('reclient/bootstrap'), + ); expect(gnCall.command, containsAllInOrder([endsWith('tools/gn'), contains('--rbe')])); expect(reclientCall.command, containsAllInOrder([endsWith('reclient/bootstrap')])); }); @@ -154,8 +162,9 @@ void main() { printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n')); expect(result, equals(0)); - print(testEnv.processHistory); - final [_, ExecutedProcess ninja, ..._] = testEnv.processHistory; + final ExecutedProcess ninja = testEnv.processHistory.firstWhere( + (p) => p.command.contains('-C'), + ); expect(ninja.command, containsAllInOrder([endsWith('ninja/ninja'), '-j', '500'])); }); @@ -268,8 +277,11 @@ void main() { ); final int result = await runner.run(['build', '--config', 'host_debug']); expect(result, equals(0)); - expect(testEnv.processHistory[1].command[0], contains(path.join('ninja', 'ninja'))); - expect(testEnv.processHistory[1].command[2], contains('local_host_debug')); + final ExecutedProcess ninja = testEnv.processHistory.firstWhere( + (p) => p.command.contains('-C'), + ); + expect(ninja.command[0], contains(path.join('ninja', 'ninja'))); + expect(ninja.command[2], contains('local_host_debug')); }); test('ci config name on the command line is correctly translated', () async { @@ -297,8 +309,11 @@ void main() { ); final int result = await runner.run(['build', '--config', 'ci/host_debug']); expect(result, equals(0)); - expect(testEnv.processHistory[1].command[0], contains(path.join('ninja', 'ninja'))); - expect(testEnv.processHistory[1].command[2], contains('ci/host_debug')); + final ExecutedProcess ninja = testEnv.processHistory.firstWhere( + (p) => p.command.contains('-C'), + ); + expect(ninja.command[0], contains(path.join('ninja', 'ninja'))); + expect(ninja.command[2], contains('ci/host_debug')); }); test('build command invokes ninja with the specified target', () async { @@ -340,7 +355,7 @@ void main() { expect(result, equals(0)); final ExecutedProcess ninjaCmd = testEnv.processHistory.firstWhere( - (p) => p.command.first.endsWith('ninja'), + (p) => p.command.first.endsWith('ninja') && p.command.contains('-C'), ); expect(ninjaCmd.command, containsAllInOrder([endsWith('ninja'), '-C', endsWith('host_debug')])); expect(ninjaCmd.command, contains(contains('flutter/fml:fml_unittests'))); @@ -389,7 +404,7 @@ void main() { expect(result, equals(0)); final ExecutedProcess ninjaCmd = testEnv.processHistory.firstWhere( - (p) => p.command.first.endsWith('ninja'), + (p) => p.command.first.endsWith('ninja') && p.command.contains('-C'), ); expect(ninjaCmd.command, containsAllInOrder([endsWith('ninja'), '-C', endsWith('host_debug')])); diff --git a/engine/src/flutter/tools/engine_tool/test/commands/run_command_test.dart b/engine/src/flutter/tools/engine_tool/test/commands/run_command_test.dart index 2d3c6c4aef1b4..d1ca5bcce7a90 100644 --- a/engine/src/flutter/tools/engine_tool/test/commands/run_command_test.dart +++ b/engine/src/flutter/tools/engine_tool/test/commands/run_command_test.dart @@ -334,7 +334,7 @@ void main() { test('builds only once if the target and host are the same', () async { await et.run(['run', '--config=host_debug']); - expect(commandsRun, containsOnce(containsAllInOrder([endsWith('ninja')]))); + expect(commandsRun, containsOnce(containsAllInOrder([endsWith('ninja'), '-C']))); }); test('builds both the target and host if they are different', () async { diff --git a/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart b/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart index b472509a234d5..d7aecb21e425f 100644 --- a/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart +++ b/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/build_config_runner.dart @@ -383,21 +383,56 @@ final class BuildRunner extends Runner { return result.ok; } + /// Generates and returns the ninja compilation database for the build. + /// + /// GN's default compile_commands.json generator only exports C and C++ + /// targets and omits custom wrapper rules like `swiftc.py`. This invokes + /// `ninja -t compdb` to retrieve all compilation targets in the build graph + /// and falls back to reading `compile_commands.json` from disk if the command + /// fails or returns empty. + Future _getCompilationDatabase() async { + final String ninjaPath = p.join( + engineSrcDir.parent.parent.path, + 'third_party', + 'ninja', + 'ninja', + ); + final String outDir = p.join(engineSrcDir.path, 'out', build.ninja.config); + final ProcessRunnerResult result = await processRunner.runProcess( + [ninjaPath, '-t', 'compdb'], + workingDirectory: io.Directory(outDir), + failOk: true, + ); + if (result.exitCode == 0 && result.stdout.isNotEmpty) { + return result.stdout; + } + final commandsFile = io.File(p.join(outDir, 'compile_commands.json')); + if (commandsFile.existsSync()) { + return commandsFile.readAsString(); + } + return null; + } + + /// Performs post-GN build steps. + /// + /// Retrieves the full compilation database from Ninja and processes it to + /// strip compiler wrapper prefixes and expand Swift compilation rules for IDE + /// language server compatibility. Future _postGn() async { if (dryRun) { return; } - final commandsFile = io.File( - p.join(engineSrcDir.path, 'out', build.ninja.config, 'compile_commands.json'), - ); - if (!commandsFile.existsSync()) { + final String? rawContents = await _getCompilationDatabase(); + if (rawContents == null) { return; } - final String contents = await commandsFile.readAsString(); - final String updated = stripCompilerWrappers(contents); - if (contents != updated) { + final String updated = updateCompilationDatabase(rawContents); + final commandsFile = io.File( + p.join(engineSrcDir.path, 'out', build.ninja.config, 'compile_commands.json'), + ); + if (rawContents != updated || !commandsFile.existsSync()) { await commandsFile.writeAsString(updated); } } diff --git a/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/update_compdb.dart b/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/update_compdb.dart index 0397eddd7fc2c..7d79ebdc210e1 100644 --- a/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/update_compdb.dart +++ b/engine/src/flutter/tools/pkg/engine_build_configs/lib/src/update_compdb.dart @@ -2,7 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert' as convert; + +import 'package:path/path.dart' as p; + +// Matches a compile_commands.json `"command"` entry up to the clang executable. +// +// Matches up to `clang`/`clang++` so any wrapper prefix (rewrapper, ccache) +// before it can be stripped. +// +// Group 1 is the leading `"command": "`. +// Group 2 is the ` clang[++]` executable. +// +// For example, given the entry: +// "command": "../../buildtools/mac-arm64/reclient/rewrapper ../../buildtools/mac-arm64/clang/bin/clang++ -c foo.cc" +// group 2 is ` ../../buildtools/mac-arm64/clang/bin/clang++`. final RegExp _clangRegexp = RegExp(r'("command"\s*:\s*").*(\s(?:\S*/)?clang(\+\+)?)(?=[\s"])'); +final RegExp _swiftEntryRegexp = RegExp(r'\{[^{}]*swiftc\.py[^{}]*\}'); +final RegExp _shellQuoteRegexp = RegExp(r'[\s"\\$`!#&*|?()<>;~]'); +const convert.JsonEncoder _jsonEncoder = convert.JsonEncoder.withIndent(' '); /// Strips compiler wrapper prefixes from compiler commands in [contents]. /// @@ -16,3 +34,320 @@ String stripCompilerWrappers(String contents) { return '${match[1]}${match[2]!.trim()}'; }); } + +/// Converts GN `swiftc.py` invocations in [contents] into native `swiftc` commands. +/// +/// Our build toolchain invokes `swiftc` from a `swiftc.py` wrapper. Language +/// servers such as SourceKit-LSP expect expanded, per-file `swiftc` invocations +/// in `compile_commands.json`. This replaces wrapper calls with direct +/// invocations that language servers understand. +String expandSwiftcCommands(String contents) { + if (!contents.contains('swiftc.py')) { + return contents; + } + + return contents.replaceAllMapped(_swiftEntryRegexp, (Match match) { + final String rawJson = match.group(0)!; + try { + final entry = convert.jsonDecode(rawJson) as Map; + final List> expanded = expandSwiftEntry(entry); + return expanded.map((Map map) => _jsonEncoder.convert(map)).join(',\n '); + } catch (_) { + // If parsing fails for any reason, leave the block untouched. + return rawJson; + } + }); +} + +/// Post-processes the contents of a `compile_commands.json` file. +/// +/// Strips compiler wrapper prefixes (such as rewrapper and ccache) from clang +/// commands, and converts GN `swiftc.py` invocations into native `swiftc` +/// commands expanded per-file for SourceKit-LSP. +String updateCompilationDatabase(String contents) { + contents = stripCompilerWrappers(contents); + return expandSwiftcCommands(contents); +} + +/// Expands a single `swiftc.py` [entry] into per-file `swiftc` compilation entries. +/// +/// If [entry] has a `command` that invokes `swiftc.py`, this parses the +/// arguments, translates them to `swiftc` arguments, makes paths absolute +/// against the entry's `directory`, and returns an entry for each `.swift` file +/// compiled. +List> expandSwiftEntry(Map entry) { + final String entryDir = (entry['directory'] as String?) ?? ''; + final Map baseEntry = _absolutizeEntryFile(entry, entryDir); + + final origCommand = baseEntry['command'] as String?; + if (origCommand == null) { + return [baseEntry]; + } + + final _SwiftcTranslation? translation = _translateSwiftcCommand(origCommand); + if (translation == null) { + return [baseEntry]; + } + + final translatedEntry = { + ...baseEntry, + 'command': _resolveCommandPaths(entryDir, translation.args), + }; + + if (translation.swiftFiles.isEmpty) { + return [translatedEntry]; + } + + final absSwiftFiles = [ + for (final String f in translation.swiftFiles) makePathAbsolute(entryDir, f), + ]; + return _duplicateEntryPerSwiftFile(translatedEntry, absSwiftFiles); +} + +/// Returns a copy of [entry] with an absolute `file` path. +Map _absolutizeEntryFile(Map entry, String entryDir) { + final file = entry['file'] as String?; + return { + ...entry, + if (file != null && !p.isAbsolute(file)) 'file': makePathAbsolute(entryDir, file), + }; +} + +/// Duplicates [entry] once per file in [absSwiftFiles]. +/// +/// This skips [entry]'s own `file`, which is already covered, overriding `file` +/// on each copy, so the language server indexes every compiled Swift file. +List> _duplicateEntryPerSwiftFile( + Map entry, + List absSwiftFiles, +) { + final origFileAbs = entry['file'] as String?; + final results = >[entry]; + for (final absFile in absSwiftFiles) { + if (absFile != origFileAbs) { + results.add({...entry, 'file': absFile}); + } + } + return results; +} + +/// Resolves [filePath] against [directory] and returns the normalized absolute path. +String makePathAbsolute(String directory, String filePath) => + p.normalize(p.isAbsolute(filePath) ? filePath : p.join(directory, filePath)); + +/// The `swiftc` arguments and Swift source files extracted from a GN `swiftc.py` command. +typedef _SwiftcTranslation = ({List args, List swiftFiles}); + +/// Parses [cmdStr] and translates it to `swiftc` arguments. +/// +/// Returns null if [cmdStr] is not a `swiftc.py` invocation. +_SwiftcTranslation? _translateSwiftcCommand(String cmdStr) { + if (!cmdStr.contains('swiftc.py')) { + return null; + } + final List words = splitShellWords(cmdStr); + final int swiftcPyIdx = words.indexWhere((String w) => w.contains('swiftc.py')); + if (swiftcPyIdx == -1) { + return null; + } + return _translateSwiftcArgs(words.sublist(swiftcPyIdx + 1)); +} + +/// Returns true if the flag [arg] is a boolean flag that does not take a value. +/// +/// Most of swiftc.py's flags take a value; this returns true for those that +/// don't. +bool _isBooleanFlag(String arg) { + return arg == '--fix-generated-header'; +} + +/// Translates GN `swiftc.py` [args] into native `swiftc` arguments. +/// +/// Non-path `-Xcc` flag synthesis (such as +/// preprocessor defines `-D`) is handled here, during syntactic argument +/// translation. +/// +/// Path-dependent `-Xcc` flag synthesis (such as `-I` or `-F`) is deferred to +/// [_resolveCommandPaths], where relative filesystem paths are resolved to +/// absolute paths. +_SwiftcTranslation _translateSwiftcArgs(List args) { + final newArgs = ['swiftc', '-parse-as-library']; + final swiftFiles = []; + + var i = 0; + while (i < args.length) { + final String arg = args[i]; + switch (arg) { + case '-import-objc-header': + // Extract header argument. + if (i + 1 < args.length) { + final String val = args[i + 1]; + if (val.isNotEmpty && val != '""' && val != "''") { + newArgs.addAll(['-import-objc-header', val]); + } + } + i += 2; + case '--whole-module-optimization': + newArgs.add('-whole-module-optimization'); + i += 1; + case _ when arg.startsWith('--'): + i += _isBooleanFlag(arg) ? 1 : 2; + case '-D': + if (i + 1 < args.length) { + final String val = args[i + 1]; + // Swift's `-D` only supports bare conditional-compilation flags, not + // `key=value` defines, so those are only forwarded to clang. + if (!val.contains('=')) { + newArgs.addAll(['-D', val]); + } + newArgs.addAll(['-Xcc', '-D$val']); + } + i += 2; + case _ when arg.startsWith('-D'): + final String val = arg.substring(2); + if (!val.contains('=')) { + newArgs.add(arg); + } + newArgs.addAll(['-Xcc', '-D$val']); + i += 1; + case _: + newArgs.add(arg); + if (arg.endsWith('.swift')) { + swiftFiles.add(arg); + } + i += 1; + } + } + + return (args: newArgs, swiftFiles: swiftFiles); +} + +/// Resolves relative paths in [words] to absolute paths against [directory]. +/// +/// Synthesizes path-dependent `-Xcc` flags for include and framework search +/// paths (`-I`, `-F`, `-isystem`, `-Fsystem`), and returns the quoted, joined +/// command string. Non-path `-Xcc` synthesis (`-D`) is handled upstream, in +/// [_translateSwiftcArgs]. +/// +/// `-isystem` is clang-only: it's forwarded to swift only as `-Xcc -isystem +/// -Xcc `, never as a bare swift-side flag. +String _resolveCommandPaths(String directory, List words) { + final newArgs = []; + + // `-isystem` is the only one of these forwarded to clang exclusively; see + // the doc comment above. + bool isClangOnly(String flag) => flag == '-isystem'; + + int addSeparatedIncludeFlag(int i) { + final String flag = words[i]; + if (!isClangOnly(flag)) { + newArgs.add(flag); + } + if (i + 1 >= words.length) { + return i + 1; + } + final String absVal = makePathAbsolute(directory, words[i + 1]); + if (!isClangOnly(flag)) { + newArgs.add(absVal); + } + if (flag case '-I' || '-isystem' || '-F' || '-Fsystem') { + newArgs.addAll(['-Xcc', flag, '-Xcc', absVal]); + } + return i + 2; + } + + void addAttachedIncludeFlag(String arg) { + final String prefix = switch (arg) { + _ when arg.startsWith('-isystem') => '-isystem', + _ when arg.startsWith('-Fsystem') => '-Fsystem', + _ when arg.startsWith('-F') => '-F', + _ => '-I', + }; + + // A bare `-I`/`-isystem`/`-F`/`-Fsystem` (no attached value) is caught by + // the exact-match branch below instead, so `val` is never empty here. + final String val = makePathAbsolute(directory, arg.substring(prefix.length)); + if (!isClangOnly(prefix)) { + newArgs.add('$prefix$val'); + } + newArgs.addAll(['-Xcc', '$prefix$val']); + } + + var i = 0; + while (i < words.length) { + final String arg = words[i]; + switch (arg) { + case _ when arg.endsWith('.swift'): + newArgs.add(makePathAbsolute(directory, arg)); + i += 1; + case '-I' || '-isystem' || '-F' || '-Fsystem' || '-import-objc-header' || '-sdk': + i = addSeparatedIncludeFlag(i); + case _ when arg.startsWith('-I') || arg.startsWith('-F') || arg.startsWith('-isystem'): + addAttachedIncludeFlag(arg); + i += 1; + case _: + newArgs.add(arg); + i += 1; + } + } + return newArgs.map(quoteShellWord).join(' '); +} + +/// Parses [cmd] into individual shell arguments. +/// +/// An empty quoted argument (`""` or `''`) is preserved as an empty-string +/// element rather than disappearing, so callers can distinguish "argument +/// present but empty" from "argument absent". +List splitShellWords(String cmd) { + final args = []; + final buffer = StringBuffer(); + var inSingleQuote = false; + var inDoubleQuote = false; + var escape = false; + var quoted = false; + + for (var i = 0; i < cmd.length; i += 1) { + final String char = cmd[i]; + switch (char) { + case _ when escape: + buffer.write(char); + escape = false; + case r'\' when !inSingleQuote: + escape = true; + case "'" when !inDoubleQuote: + inSingleQuote = !inSingleQuote; + quoted = true; + case '"' when !inSingleQuote: + inDoubleQuote = !inDoubleQuote; + quoted = true; + case ' ' || '\t' when !inSingleQuote && !inDoubleQuote: + if (buffer.isNotEmpty || quoted) { + args.add(buffer.toString()); + buffer.clear(); + quoted = false; + } + case _: + buffer.write(char); + } + } + if (buffer.isNotEmpty || quoted) { + args.add(buffer.toString()); + } + return args; +} + +/// Quotes and escapes [arg] for safe inclusion as a shell argument. +String quoteShellWord(String arg) { + if (arg.isEmpty) { + return "''"; + } + if (!arg.contains(_shellQuoteRegexp)) { + return arg; + } + if (!arg.contains("'")) { + return "'$arg'"; + } + return r'"' + '${arg.replaceAll(r'\', r'\\').replaceAll('"', r'\"')}' + r'"'; +} diff --git a/engine/src/flutter/tools/pkg/engine_build_configs/test/update_compdb_test.dart b/engine/src/flutter/tools/pkg/engine_build_configs/test/update_compdb_test.dart index 81678fcce6e1a..96df159482a19 100644 --- a/engine/src/flutter/tools/pkg/engine_build_configs/test/update_compdb_test.dart +++ b/engine/src/flutter/tools/pkg/engine_build_configs/test/update_compdb_test.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:convert' as convert; + import 'package:engine_build_configs/src/update_compdb.dart'; import 'package:test/test.dart'; @@ -26,6 +28,7 @@ void main() { } ] '''; + expect(updateCompilationDatabase(input), equals(expected)); expect(stripCompilerWrappers(input), equals(expected)); }); @@ -48,6 +51,7 @@ void main() { } ] '''; + expect(updateCompilationDatabase(input), equals(expected)); expect(stripCompilerWrappers(input), equals(expected)); }); @@ -70,6 +74,7 @@ void main() { } ] '''; + expect(updateCompilationDatabase(input), equals(expected)); expect(stripCompilerWrappers(input), equals(expected)); }); @@ -83,7 +88,220 @@ void main() { } ] '''; + expect(updateCompilationDatabase(input), equals(input)); expect(stripCompilerWrappers(input), equals(input)); }); }); + + group('expandSwiftcCommands', () { + test('leaves standard compile_commands untouched if no swiftc.py or wrapper is present', () { + const input = r''' +[ + { + "file": "../../flutter/foo.cc", + "directory": "/out/config", + "command": "../../clang/bin/clang++ -c ../../flutter/foo.cc" + } +] +'''; + expect(updateCompilationDatabase(input), equals(input)); + expect(stripCompilerWrappers(input), equals(input)); + expect(expandSwiftcCommands(input), equals(input)); + }); + + test('translates swiftc.py flags and makes paths absolute', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar -import-objc-header ../../flutter/header.h -target arm64-macos14.0 -I ../../flutter/include -D FOO_BAR ../../flutter/bar.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + expect(json.length, equals(1)); + final entry = json[0] as Map; + expect(entry['file'], equals('/flutter/bar.swift')); + final command = entry['command'] as String; + expect(command, contains('swiftc')); + expect(command, contains('-parse-as-library')); + expect(command, contains('-module-name Bar')); + expect(command, contains('-import-objc-header /flutter/header.h')); + expect(command, contains('-target arm64-macos14.0')); + expect(command, contains('-I /flutter/include')); + expect(command, contains('-Xcc -I -Xcc /flutter/include')); + expect(command, contains('-D FOO_BAR')); + expect(command, contains('-Xcc -DFOO_BAR')); + }); + + test('-isystem is forwarded to clang only, never bare to swiftc', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar -isystem ../../flutter/sysinclude ../../flutter/bar.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + final entry = json[0] as Map; + final List args = splitShellWords(entry['command'] as String); + expect( + args, + equals([ + 'swiftc', + '-parse-as-library', + '-module-name', + 'Bar', + '-Xcc', + '-isystem', + '-Xcc', + '/flutter/sysinclude', + '/flutter/bar.swift', + ]), + ); + }); + + test('-F and -Fsystem are forwarded to both swiftc and clang', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar -F ../../flutter/frameworks -Fsystem../../flutter/sysframeworks ../../flutter/bar.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + final entry = json[0] as Map; + final List args = splitShellWords(entry['command'] as String); + expect( + args, + equals([ + 'swiftc', + '-parse-as-library', + '-module-name', + 'Bar', + '-F', + '/flutter/frameworks', + '-Xcc', + '-F', + '-Xcc', + '/flutter/frameworks', + '-Fsystem/flutter/sysframeworks', + '-Xcc', + '-Fsystem/flutter/sysframeworks', + '/flutter/bar.swift', + ]), + ); + }); + + test('-D with a value ("key=value") is forwarded to clang only', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar -D FOO=1 -DBAR=2 ../../flutter/bar.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + final entry = json[0] as Map; + final List args = splitShellWords(entry['command'] as String); + expect( + args, + equals([ + 'swiftc', + '-parse-as-library', + '-module-name', + 'Bar', + '-Xcc', + '-DFOO=1', + '-Xcc', + '-DBAR=2', + '/flutter/bar.swift', + ]), + ); + }); + + test('drops -import-objc-header when the value is an empty quoted string', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar -import-objc-header \"\" -target arm64-macos14.0 ../../flutter/bar.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + final entry = json[0] as Map; + final command = entry['command'] as String; + expect(command, isNot(contains('-import-objc-header'))); + expect(command, contains('-module-name Bar')); + expect(command, contains('-target arm64-macos14.0')); + }); + + test('leaves a malformed swiftc.py JSON block untouched', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift" + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar ../../flutter/bar.swift" + } +] +'''; + expect(expandSwiftcCommands(input), equals(input)); + }); + + test('expands multi-file swiftc.py invocations into individual entries', () { + const input = r''' +[ + { + "file": "../../flutter/bar.swift", + "directory": "/out/config", + "command": "python3 ../../flutter/tools/swiftc.py -module-name Bar ../../flutter/bar.swift ../../flutter/baz.swift" + } +] +'''; + final String output = updateCompilationDatabase(input); + final json = convert.jsonDecode(output) as List; + expect(json.length, equals(2)); + final entry1 = json[0] as Map; + final entry2 = json[1] as Map; + expect(entry1['file'], equals('/flutter/bar.swift')); + expect(entry2['file'], equals('/flutter/baz.swift')); + expect(entry1['command'], equals(entry2['command'])); + }); + }); + + group('splitShellWords / quoteShellWord', () { + test('splitShellWords and quoteShellWord work correctly', () { + final List words = splitShellWords("python3 'foo bar' \"baz qux\" -D \"\\\$FOO\""); + expect(words, equals(['python3', 'foo bar', 'baz qux', '-D', r'$FOO'])); + expect(quoteShellWord('foo bar'), equals("'foo bar'")); + expect(quoteShellWord('simple'), equals('simple')); + }); + + test('splitShellWords handles edge cases gracefully', () { + expect(splitShellWords(''), isEmpty); + expect(splitShellWords(' \t '), isEmpty); + expect(splitShellWords(r'foo\'), equals(['foo'])); + expect(quoteShellWord(''), equals("''")); + }); + + test('splitShellWords preserves an empty quoted argument as an empty string', () { + expect(splitShellWords('-foo "" -bar'), equals(['-foo', '', '-bar'])); + expect(splitShellWords("-foo '' -bar"), equals(['-foo', '', '-bar'])); + }); + }); } diff --git a/engine/src/flutter/tools/vscode_workspace/engine-workspace.yaml b/engine/src/flutter/tools/vscode_workspace/engine-workspace.yaml index e70e3504e0d57..9e794046ac1ca 100644 --- a/engine/src/flutter/tools/vscode_workspace/engine-workspace.yaml +++ b/engine/src/flutter/tools/vscode_workspace/engine-workspace.yaml @@ -129,6 +129,8 @@ settings: - ${workspaceFolder} dotnet.defaultSolution: disable dart.showTodos: false + swift.sourcekit-lsp.supported-languages: + - swift testMate.cpp.test.advancedExecutables: - name: impeller_unittests_arm64 pattern: ../out/host_debug_unopt_arm64/impeller_unittests From 34e2485046854a07044b73b6ae524af178c1e1bc Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:29:08 +0000 Subject: [PATCH 011/147] Adds skill for generating engine diffs for new releases. (#189869) Adds a skill for generating a "what's new" for the flutter engine across branches. This skill wasn't able to generate the information directly needed for a blog post but it was informative to writing highlights for a blog post. It's probably worth checking this in and trying to refine it further in the future. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .agents/skills/engine-whats-new/SKILL.md | 72 +++ .../skills/engine-whats-new/scripts/README.md | 22 + .../scripts/generate_engine_whats_new.dart | 553 ++++++++++++++++++ 3 files changed, 647 insertions(+) create mode 100644 .agents/skills/engine-whats-new/SKILL.md create mode 100644 .agents/skills/engine-whats-new/scripts/README.md create mode 100644 .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart diff --git a/.agents/skills/engine-whats-new/SKILL.md b/.agents/skills/engine-whats-new/SKILL.md new file mode 100644 index 0000000000000..b6adc9e9b9f26 --- /dev/null +++ b/.agents/skills/engine-whats-new/SKILL.md @@ -0,0 +1,72 @@ +--- +name: engine-whats-new +description: > + Generates the "what's new" release summary and diff file for changes in the Flutter engine (//engine/src/flutter) between two releases (e.g., 3.47 vs 3.44). + + When to use: + - Only activate when explicitly asked by the user to generate what's new in the engine, diff Flutter engine releases, or produce an engine release summary. + + When not to use: + - Do not use for general questions about commits, individual file history, or codebase searches unless explicitly requested to generate the engine release diff or summary. +--- + +# Flutter Engine What's New & Diff Skill + +## Workflow + +### 1. Identify Inputs + +Extract the following from the user's request: +* **Target Release (``):** The Flutter release version to analyze (e.g., `3.47`, `3.47.0`, or `flutter-3.47-candidate.0`). +* **Base Release (``):** Optional. The prior Flutter release to compare against (e.g., `3.44`). If omitted by the user, the script will automatically calculate the predecessor release (e.g., for `3.47` it automatically selects `3.44`). + +### 2. Run the Generator Tool + +Execute the Dart script from the Flutter repository root: + +```bash +dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release +``` + +If the user specifies a custom base release or output paths: + +```bash +dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release --from --output-diff engine_diff__to_.diff --output-summary engine_whats_new_.md +``` + +To output structured JSON for programmatic consumption: + +```bash +dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release --format json +``` + +### 3. Review Generated Artifacts & Present to User + +The tool produces two primary artifacts in the repository root: +1. **Diff File (`engine_diff__to_.diff`):** The full unified git diff of all changes in //engine/src/flutter between the two releases. +2. **Summary Document (`engine_whats_new_.md`):** A categorized Markdown summary covering: + * **Overview & Statistics:** Total engine commits, files changed, additions, and deletions. + * **🚀 Impeller & Graphics Rendering:** Vulkan, Metal, OpenGL, shaders, and display list updates. + * **🌐 Web Engine & Wasm:** Web SDK, CanvasKit, and WebAssembly changes. + * **📱 Android Embedding:** Gradle/AGP updates, Android view rendering, and Java/Kotlin embedding changes. + * **🍎 iOS & macOS Embeddings:** Darwin platform view lifecycle, Metal views, and macOS/iOS updates. + * **🪟 Windows & Linux Desktop Embeddings:** Win32 compositor, Linux GTK embedding, and desktop shell fixes. + * **🔤 Text, Typography & Accessibility:** Semantics, IME, font fallback, and text layout improvements. + * **🔄 Dependency Rolls:** Skia, Dart SDK, ICU, HarfBuzz, and ANGLE rolls. + * **🛠️ Build System, CI & Tooling:** GN build files, luci scripts, and testing utilities. + +Provide the user with a concise overview of the results and links to the generated diff and markdown summary files. + +## Examples + +- **User:** "Generate what's new in the engine for Flutter 3.47." +- **Agent:** + 1. Identifies target release `3.47`. + 2. Runs `dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release 3.47`. + 3. Reports the summary statistics (e.g., 354 commits, 1179 files changed) and shares the generated `engine_diff_3.44_to_3.47.diff` and `engine_whats_new_3.47.md`. + +- **User:** "Diff the engine changes between Flutter 3.41 and 3.44." +- **Agent:** + 1. Identifies base release `3.41` and target release `3.44`. + 2. Runs `dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --from 3.41 --to 3.44`. + 3. Presents the breakdown of engine changes and the diff file. diff --git a/.agents/skills/engine-whats-new/scripts/README.md b/.agents/skills/engine-whats-new/scripts/README.md new file mode 100644 index 0000000000000..d0775acd8f844 --- /dev/null +++ b/.agents/skills/engine-whats-new/scripts/README.md @@ -0,0 +1,22 @@ +# Flutter Engine What's New Tool + +This directory contains helper scripts for the `engine-whats-new` skill. + +## `generate_engine_whats_new.dart` + +Generates the "What's New" summary and diff file for changes in the Flutter engine directory (`//engine/src/flutter`) between two Flutter releases. + +### Usage + +```bash +dart .agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart --release [--from ] +``` + +### Options + +* `--release`, `--to`, `--target `: Target Flutter release version (e.g., `3.47`). +* `--from`, `--base `: Base Flutter release version (e.g., `3.44`). If omitted, the tool automatically deduces the preceding quarterly release. +* `--engine-path `: Path to the Flutter engine directory relative to the repository root (defaults to `engine/src/flutter`). +* `--output-diff `: Filepath to write the generated diff (default: `engine_diff__to_.diff`). +* `--output-summary `: Filepath to write the Markdown summary (default: `engine_whats_new_.md`). +* `--format `: Stdout output format (default: `markdown`). diff --git a/.agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart b/.agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart new file mode 100644 index 0000000000000..debbbc8e4daed --- /dev/null +++ b/.agents/skills/engine-whats-new/scripts/generate_engine_whats_new.dart @@ -0,0 +1,553 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert'; +import 'dart:io'; + +class CommitInfo { + CommitInfo({ + required this.hash, + required this.shortHash, + required this.author, + required this.date, + required this.title, + this.prNumber, + }); + + final String hash; + final String shortHash; + final String author; + final String date; + final String title; + final String? prNumber; + + Map toJson() => { + 'hash': hash, + 'shortHash': shortHash, + 'author': author, + 'date': date, + 'title': title, + 'prNumber': prNumber, + }; +} + +class ReleaseAnalysis { + ReleaseAnalysis({ + required this.baseRelease, + required this.baseRef, + required this.targetRelease, + required this.targetRef, + required this.enginePath, + required this.diffPath, + required this.summaryPath, + required this.totalCommits, + required this.filesChanged, + required this.insertions, + required this.deletions, + required this.categorizedCommits, + }); + + final String baseRelease; + final String baseRef; + final String targetRelease; + final String targetRef; + final String enginePath; + final String diffPath; + final String summaryPath; + final int totalCommits; + final int filesChanged; + final int insertions; + final int deletions; + final Map> categorizedCommits; + + Map toJson() => { + 'baseRelease': baseRelease, + 'baseRef': baseRef, + 'targetRelease': targetRelease, + 'targetRef': targetRef, + 'enginePath': enginePath, + 'diffPath': diffPath, + 'summaryPath': summaryPath, + 'stats': { + 'totalCommits': totalCommits, + 'filesChanged': filesChanged, + 'insertions': insertions, + 'deletions': deletions, + }, + 'categories': categorizedCommits.map( + (String key, List value) => + MapEntry(key, value.map((CommitInfo c) => c.toJson()).toList()), + ), + }; +} + +Directory findRepoRoot() { + Directory dir = Directory.current; + while (dir.path != dir.parent.path) { + if (Directory('${dir.path}/.git').existsSync()) { + return dir; + } + dir = dir.parent; + } + return Directory.current; +} + +ProcessResult runGit(List args, {required String workingDirectory}) { + final ProcessResult result = Process.runSync('git', args, workingDirectory: workingDirectory); + if (result.exitCode != 0) { + throw ProcessException('git', args, result.stderr.toString(), result.exitCode); + } + return result; +} + +String? tryResolveGitRef(String version, String repoRoot) { + final String cleanVersion = version.trim(); + final candidates = [ + cleanVersion, + if (!cleanVersion.contains('.')) '3.$cleanVersion.0', + if (cleanVersion.startsWith('3.') && + !cleanVersion.contains('-') && + cleanVersion.split('.').length == 2) + '$cleanVersion.0', + 'origin/flutter-$cleanVersion-candidate.0', + 'flutter-$cleanVersion-candidate.0', + if (cleanVersion.startsWith('3.')) + 'origin/flutter-${cleanVersion.split('.').take(2).join('.')}-candidate.0', + if (cleanVersion.startsWith('3.')) + 'flutter-${cleanVersion.split('.').take(2).join('.')}-candidate.0', + 'v$cleanVersion', + 'v$cleanVersion.0', + ]; + + for (final candidate in candidates) { + try { + final ProcessResult res = Process.runSync('git', [ + 'rev-parse', + '--verify', + candidate, + ], workingDirectory: repoRoot); + if (res.exitCode == 0) { + return candidate; + } + } catch (_) {} + } + + try { + final ProcessResult tagResult = Process.runSync('git', [ + 'tag', + '-l', + '*$cleanVersion*', + ], workingDirectory: repoRoot); + if (tagResult.exitCode == 0) { + final tagOutput = tagResult.stdout as String; + final List tags = tagOutput + .split('\n') + .map((String s) => s.trim()) + .where((String s) => s.isNotEmpty) + .toList(); + if (tags.isNotEmpty) { + for (final tag in tags) { + if (tag == cleanVersion || tag == '$cleanVersion.0') { + return tag; + } + } + return tags.first; + } + } + } catch (_) {} + + return null; +} + +String deducePreviousRelease(String targetRelease) { + final String normalized = targetRelease + .replaceFirst('flutter-', '') + .replaceFirst('-candidate.0', ''); + final List parts = normalized.split('.'); + if (parts.length >= 2 && parts[0] == '3') { + final int? minor = int.tryParse(parts[1]); + if (minor != null) { + int prevMinor = minor - 3; + if (prevMinor == 28) { + prevMinor = 27; + } + if (prevMinor > 0) { + return '3.$prevMinor'; + } + } + } + return ''; +} + +String? extractPrNumber(String title) { + final RegExpMatch? match = RegExp(r'#(\d+)').firstMatch(title); + return match?.group(1); +} + +String categorizeCommit(String title) { + final String lower = title.toLowerCase(); + + if (title.startsWith('Roll Skia') || + title.startsWith('Roll Dart SDK') || + title.startsWith('Roll ICU') || + title.startsWith('Roll HarfBuzz') || + title.startsWith('Roll ANGLE') || + lower.contains('roll skia') || + lower.contains('roll dart sdk') || + lower.contains('roll icu')) { + return '🔄 Dependency Rolls'; + } + + if (lower.contains('impeller') || + lower.contains('ubersdf') || + lower.contains('flutter gpu') || + lower.contains('display_list') || + lower.contains('displaylist') || + lower.contains('vulkan') || + lower.contains('metal') || + lower.contains('opengl') || + lower.contains('shader') || + lower.contains('render') || + lower.contains('flow')) { + return '🚀 Impeller & Graphics Rendering'; + } + + if (lower.contains('[web]') || + lower.contains('web_ui') || + lower.contains('web_sdk') || + lower.contains('wasm') || + lower.contains('skwasm') || + lower.contains('html') || + lower.contains('canvaskit')) { + return '🌐 Web Engine & Wasm'; + } + + if (lower.contains('[android]') || + lower.contains('android') || + lower.contains('agp') || + lower.contains('gradle') || + lower.contains('embedding/engine')) { + return '📱 Android Embedding'; + } + + if (lower.contains('[ios]') || + lower.contains('[macos]') || + lower.contains('[darwin]') || + lower.contains('darwin') || + lower.contains('ios') || + lower.contains('macos') || + lower.contains('xcode') || + lower.contains('metalview')) { + return '🍎 iOS & macOS Embeddings'; + } + + if (lower.contains('[windows]') || + lower.contains('[linux]') || + lower.contains('windows') || + lower.contains('linux') || + lower.contains('win32') || + lower.contains('embedder')) { + return '🪟 Windows & Linux Desktop Embeddings'; + } + + if (lower.contains('[a11y]') || + lower.contains('semantics') || + lower.contains('accessibility') || + lower.contains('typography') || + lower.contains('txt') || + lower.contains('font') || + lower.contains('text input') || + lower.contains('autofill')) { + return '🔤 Text, Typography & Accessibility'; + } + + if (lower.contains('[ci]') || + lower.contains('ci:') || + lower.contains('build.gn') || + lower.contains('tools') || + lower.contains('testing') || + lower.contains('header_guard') || + lower.contains('license') || + lower.contains('format')) { + return '🛠️ Build System, CI & Tooling'; + } + + return '⚙️ Core Runtime & Shell'; +} + +ReleaseAnalysis analyzeEngineDiff({ + required String repoRoot, + required String baseRelease, + required String targetRelease, + String enginePath = 'engine/src/flutter', + String? outputDiffPath, + String? outputSummaryPath, +}) { + final String? baseRef = tryResolveGitRef(baseRelease, repoRoot); + if (baseRef == null) { + throw ArgumentError('Could not resolve git reference for base release "$baseRelease".'); + } + + final String? targetRef = tryResolveGitRef(targetRelease, repoRoot); + if (targetRef == null) { + throw ArgumentError('Could not resolve git reference for target release "$targetRelease".'); + } + + final String resolvedDiffPath = + outputDiffPath ?? + 'engine_diff_${baseRelease.replaceAll('/', '_')}_to_${targetRelease.replaceAll('/', '_')}.diff'; + final String resolvedSummaryPath = + outputSummaryPath ?? 'engine_whats_new_${targetRelease.replaceAll('/', '_')}.md'; + + final ProcessResult diffResult = runGit([ + 'diff', + '$baseRef..$targetRef', + '--', + enginePath, + ], workingDirectory: repoRoot); + File('$repoRoot/$resolvedDiffPath').writeAsStringSync(diffResult.stdout as String); + + var filesChanged = 0; + var insertions = 0; + var deletions = 0; + + final ProcessResult statResult = runGit([ + 'diff', + '--shortstat', + '$baseRef..$targetRef', + '--', + enginePath, + ], workingDirectory: repoRoot); + final String statStr = (statResult.stdout as String).trim(); + if (statStr.isNotEmpty) { + final RegExpMatch? filesMatch = RegExp(r'(\d+)\s+files? changed').firstMatch(statStr); + final RegExpMatch? insMatch = RegExp(r'(\d+)\s+insertions?\(\+\)').firstMatch(statStr); + final RegExpMatch? delMatch = RegExp(r'(\d+)\s+deletions?\(-\)').firstMatch(statStr); + + if (filesMatch != null) { + filesChanged = int.parse(filesMatch.group(1)!); + } + if (insMatch != null) { + insertions = int.parse(insMatch.group(1)!); + } + if (delMatch != null) { + deletions = int.parse(delMatch.group(1)!); + } + } + + final ProcessResult logResult = runGit([ + 'log', + '--pretty=format:%H%x09%h%x09%an%x09%ad%x09%s', + '--date=short', + '$baseRef..$targetRef', + '--', + enginePath, + ], workingDirectory: repoRoot); + + final List lines = (logResult.stdout as String) + .split('\n') + .map((String s) => s.trim()) + .where((String s) => s.isNotEmpty) + .toList(); + + final categorized = >{ + '🚀 Impeller & Graphics Rendering': [], + '🌐 Web Engine & Wasm': [], + '📱 Android Embedding': [], + '🍎 iOS & macOS Embeddings': [], + '🪟 Windows & Linux Desktop Embeddings': [], + '🔤 Text, Typography & Accessibility': [], + '🔄 Dependency Rolls': [], + '🛠️ Build System, CI & Tooling': [], + '⚙️ Core Runtime & Shell': [], + }; + + for (final line in lines) { + final List parts = line.split('\t'); + if (parts.length >= 5) { + final String hash = parts[0]; + final String shortHash = parts[1]; + final String author = parts[2]; + final String date = parts[3]; + final String title = parts.sublist(4).join('\t'); + final String? pr = extractPrNumber(title); + + final commit = CommitInfo( + hash: hash, + shortHash: shortHash, + author: author, + date: date, + title: title, + prNumber: pr, + ); + + final String cat = categorizeCommit(title); + categorized.putIfAbsent(cat, () => []).add(commit); + } + } + + final analysis = ReleaseAnalysis( + baseRelease: baseRelease, + baseRef: baseRef, + targetRelease: targetRelease, + targetRef: targetRef, + enginePath: enginePath, + diffPath: resolvedDiffPath, + summaryPath: resolvedSummaryPath, + totalCommits: lines.length, + filesChanged: filesChanged, + insertions: insertions, + deletions: deletions, + categorizedCommits: categorized, + ); + + final String summaryContent = generateMarkdownSummary(analysis); + File('$repoRoot/$resolvedSummaryPath').writeAsStringSync(summaryContent); + + return analysis; +} + +String generateMarkdownSummary(ReleaseAnalysis analysis) { + final buffer = StringBuffer(); + + buffer.writeln("# What's New in Flutter Engine (Release ${analysis.targetRelease})"); + buffer.writeln(); + buffer.writeln( + 'Comparing changes in `//${analysis.enginePath}` from **${analysis.baseRelease}** (`${analysis.baseRef}`) to **${analysis.targetRelease}** (`${analysis.targetRef}`).', + ); + buffer.writeln(); + buffer.writeln('## 📊 Overview & Statistics'); + buffer.writeln(); + buffer.writeln('- **Diff File Generated:** [${analysis.diffPath}](${analysis.diffPath})'); + buffer.writeln('- **Total Engine Commits:** ${analysis.totalCommits}'); + buffer.writeln('- **Files Changed:** ${analysis.filesChanged}'); + buffer.writeln('- **Lines Added:** +${analysis.insertions}'); + buffer.writeln('- **Lines Removed:** -${analysis.deletions}'); + buffer.writeln(); + + buffer.writeln('## 🌟 Subsystem Breakdown'); + buffer.writeln(); + + for (final MapEntry> entry in analysis.categorizedCommits.entries) { + if (entry.value.isEmpty) { + continue; + } + buffer.writeln('### ${entry.key} (${entry.value.length} commits)'); + buffer.writeln(); + for (final CommitInfo c in entry.value) { + final String prLink = c.prNumber != null + ? '[#${c.prNumber}](https://github.com/flutter/flutter/pull/${c.prNumber})' + : c.shortHash; + buffer.writeln('- ${c.title} ($prLink by *${c.author}*)'); + } + buffer.writeln(); + } + + return buffer.toString(); +} + +void printHelp() { + stdout.writeln("Flutter Engine What's New & Diff Generator"); + stdout.writeln(); + stdout.writeln('Usage:'); + stdout.writeln( + ' dart generate_engine_whats_new.dart --release [--from ]', + ); + stdout.writeln(); + stdout.writeln('Options:'); + stdout.writeln( + ' --release, --to, --target Target Flutter release version (e.g. 3.47)', + ); + stdout.writeln( + ' --from, --base Base Flutter release version (e.g. 3.44). If omitted, automatically determined.', + ); + stdout.writeln( + ' --engine-path Engine directory relative to repo root (default: engine/src/flutter)', + ); + stdout.writeln( + ' --output-diff Output diff file path (default: engine_diff__to_.diff)', + ); + stdout.writeln( + ' --output-summary Output summary markdown file path (default: engine_whats_new_.md)', + ); + stdout.writeln( + ' --format Stdout output format (default: markdown)', + ); + stdout.writeln(' -h, --help Show this help message'); +} + +void main(List args) { + if (args.isEmpty || args.contains('-h') || args.contains('--help')) { + printHelp(); + exit(args.isEmpty ? 1 : 0); + } + + String? targetRelease; + String? baseRelease; + var enginePath = 'engine/src/flutter'; + String? outputDiff; + String? outputSummary; + var format = 'markdown'; + + for (var i = 0; i < args.length; i++) { + final String arg = args[i]; + if ((arg == '--release' || arg == '--to' || arg == '--target') && i + 1 < args.length) { + targetRelease = args[++i]; + } else if ((arg == '--from' || arg == '--base') && i + 1 < args.length) { + baseRelease = args[++i]; + } else if (arg == '--engine-path' && i + 1 < args.length) { + enginePath = args[++i]; + } else if (arg == '--output-diff' && i + 1 < args.length) { + outputDiff = args[++i]; + } else if (arg == '--output-summary' && i + 1 < args.length) { + outputSummary = args[++i]; + } else if (arg == '--format' && i + 1 < args.length) { + format = args[++i].toLowerCase(); + } else if (!arg.startsWith('-') && targetRelease == null) { + targetRelease = arg; + } + } + + if (targetRelease == null) { + stderr.writeln('Error: Target release version is required (e.g. --release 3.47).'); + printHelp(); + exit(1); + } + + final String repoRoot = findRepoRoot().path; + + if (baseRelease == null || baseRelease.isEmpty) { + baseRelease = deducePreviousRelease(targetRelease); + if (baseRelease.isEmpty) { + stderr.writeln( + 'Error: Could not automatically deduce previous release for "$targetRelease". Please specify --from .', + ); + exit(1); + } + } + + try { + final ReleaseAnalysis analysis = analyzeEngineDiff( + repoRoot: repoRoot, + baseRelease: baseRelease, + targetRelease: targetRelease, + enginePath: enginePath, + outputDiffPath: outputDiff, + outputSummaryPath: outputSummary, + ); + + if (format == 'json') { + stdout.writeln(jsonEncode(analysis.toJson())); + } else { + stdout.writeln('Generated Engine Diff: ${analysis.diffPath}'); + stdout.writeln("Generated What's New Summary: ${analysis.summaryPath}"); + stdout.writeln( + 'Total Commits: ${analysis.totalCommits} | Files Changed: ${analysis.filesChanged} (+${analysis.insertions} / -${analysis.deletions})', + ); + } + } catch (e) { + stderr.writeln("Error generating engine what's new: $e"); + exit(1); + } +} From d78122fad75054cafa9e7f155207b1ffa5268d08 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 22 Jul 2026 22:31:13 +0000 Subject: [PATCH 012/147] Roll Dart SDK from 1e65011ee004 to 9258584f98b8 (7 revisions) (#189883) https://dart.googlesource.com/sdk.git/+log/1e65011ee004..9258584f98b8 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-53.0.dev 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-52.0.dev 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-51.0.dev 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-50.0.dev 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-49.0.dev 2026-07-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-48.0.dev 2026-07-21 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-47.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jsimmons@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index dcdf366846475..f4f51fcf34a3e 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '1e65011ee00428473ccf6dfdfa83b9700855df29', + 'dart_revision': '9258584f98b891048c32450dbb4018d4e27e227e', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From c72bd32b3788b3a44c837a38732a317f65da2e71 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 22 Jul 2026 23:15:19 +0000 Subject: [PATCH 013/147] Listen to log reader before VM Service and make delay configurable (#187202) - Listen to device log reader before VM Service connection in both `flutter drive` and `flutter run` (Resident Runner) to ensure logs are captured even if startup fails. - Wrap VM Service connection in a try-catch and apply a configurable log flush delay (default 500ms) to allow buffered logs to arrive before rethrowing. - Make the delay configurable (`logFlushDelay`) in `FlutterDriverService` and `FlutterDevice`, defaulting to `Duration.zero` in tests to keep them fast. - Add tests in `resident_runner_test.dart` and update `drive_service_test.dart` to verify the behavior. Fixes: #162087 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Ben Konyi --- .../lib/src/drive/drive_service.dart | 49 ++++++++----- .../lib/src/resident_runner.dart | 28 +++++--- .../drive/drive_service_test.dart | 72 ++++++++++++++++++- .../resident_runner_helpers.dart | 10 ++- .../general.shard/resident_runner_test.dart | 14 ++++ 5 files changed, 143 insertions(+), 30 deletions(-) diff --git a/packages/flutter_tools/lib/src/drive/drive_service.dart b/packages/flutter_tools/lib/src/drive/drive_service.dart index b1d546123a08c..8f519d549d74f 100644 --- a/packages/flutter_tools/lib/src/drive/drive_service.dart +++ b/packages/flutter_tools/lib/src/drive/drive_service.dart @@ -124,13 +124,15 @@ class FlutterDriverService extends DriverService { required String dartSdkPath, required DevtoolsLauncher devtoolsLauncher, @visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService, + @visibleForTesting Duration logFlushDelay = const Duration(milliseconds: 500), }) : _applicationPackageFactory = applicationPackageFactory, _logger = logger, _platform = platform, _processUtils = processUtils, _dartSdkPath = dartSdkPath, _vmServiceConnector = vmServiceConnector, - _devtoolsLauncher = devtoolsLauncher; + _devtoolsLauncher = devtoolsLauncher, + _logFlushDelay = logFlushDelay; static const _kLaunchAttempts = 3; @@ -141,6 +143,7 @@ class FlutterDriverService extends DriverService { final String _dartSdkPath; final VMServiceConnector _vmServiceConnector; final DevtoolsLauncher _devtoolsLauncher; + final Duration _logFlushDelay; Device? _device; ApplicationPackage? _applicationPackage; @@ -216,26 +219,34 @@ class FlutterDriverService extends DriverService { } _vmServiceUri = uri.toString(); _device = device; - if (debuggingOptions.enableDds) { - try { - await device.dds.startDartDevelopmentServiceFromDebuggingOptions( - uri, - appName: - 'Kind: Flutter - Device: ${device.displayName} - ' - 'Package: ${_applicationPackage?.name}', - debuggingOptions: debuggingOptions, - ); - _vmServiceUri = device.dds.uri.toString(); - } on DartDevelopmentServiceException { - // If there's another flutter_tools instance still connected to the target - // application, DDS will already be running remotely and this call will fail. - // This can be ignored to continue to use the existing remote DDS instance. - } - } - _vmService = await _vmServiceConnector(uri, device: _device, logger: _logger); + final DeviceLogReader logReader = await device.getLogReader(app: _applicationPackage); logReader.logLines.listen(_logger.printStatus); - await logReader.provideVmService(_vmService); + + try { + if (debuggingOptions.enableDds) { + try { + await device.dds.startDartDevelopmentServiceFromDebuggingOptions( + uri, + appName: + 'Kind: Flutter - Device: ${device.displayName} - ' + 'Package: ${_applicationPackage?.name}', + debuggingOptions: debuggingOptions, + ); + _vmServiceUri = device.dds.uri.toString(); + } on DartDevelopmentServiceException { + // If there's another flutter_tools instance still connected to the target + // application, DDS will already be running remotely and this call will fail. + // This can be ignored to continue to use the existing remote DDS instance. + } + } + _vmService = await _vmServiceConnector(uri, device: _device, logger: _logger); + await logReader.provideVmService(_vmService); + } catch (error) { + // Allow time for buffered/async log messages (e.g. engine crash logs) to arrive and flush. + await Future.delayed(_logFlushDelay); + rethrow; + } } @override diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index fb85b532ef588..12d366f9f066c 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -48,8 +48,11 @@ class FlutterDevice { required this.generator, required this.developmentShaderCompiler, this.userIdentifier, + @visibleForTesting this.logFlushDelay = const Duration(milliseconds: 500), }); + final Duration logFlushDelay; + /// Create a [FlutterDevice] with optional code generation enabled. static Future create( Device device, { @@ -1287,14 +1290,23 @@ abstract class ResidentRunner extends ResidentHandlers { _finished = Completer(); // Listen for service protocol connection to close. for (final FlutterDevice? device in flutterDevices) { - await device!.connect( - debuggingOptions: debuggingOptions, - reloadSources: reloadSources, - restart: restart, - compileExpression: compileExpression, - hostVmServicePort: debuggingOptions.hostVmServicePort, - printStructuredErrorLogMethod: printStructuredErrorLog, - ); + if (device == null) { + continue; + } + try { + await device.connect( + debuggingOptions: debuggingOptions, + reloadSources: reloadSources, + restart: restart, + compileExpression: compileExpression, + hostVmServicePort: debuggingOptions.hostVmServicePort, + printStructuredErrorLogMethod: printStructuredErrorLog, + ); + } catch (error) { + // Allow time for buffered/async log messages (e.g. engine crash logs) to arrive and flush. + await Future.delayed(device.logFlushDelay); + rethrow; + } await device.vmService!.getFlutterViews(); // This hooks up callbacks for when the connection stops in the future. diff --git a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart index 11b393ce2661e..594773bebdf9f 100644 --- a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart @@ -404,6 +404,50 @@ void main() { ); await driverService.stop(); }); + + testWithoutContext( + 'Listens to device log reader even if connection to VM service fails', + () async { + final processManager = FakeProcessManager.empty(); + final logReader = FakeDeviceLogReader(); + final DriverService driverService = FlutterDriverService( + applicationPackageFactory: FakeApplicationPackageFactory(FakeApplicationPackage()), + logger: BufferLogger.test(), + platform: FakePlatform(), + processUtils: ProcessUtils(logger: BufferLogger.test(), processManager: processManager), + dartSdkPath: 'dart', + devtoolsLauncher: FakeDevtoolsLauncher(), + logFlushDelay: Duration.zero, + vmServiceConnector: + ( + Uri httpUri, { + ReloadSources? reloadSources, + Restart? restart, + CompileExpression? compileExpression, + FlutterProject? flutterProject, + PrintStructuredErrorLogMethod? printStructuredErrorLogMethod, + io.CompressionOptions compression = io.CompressionOptions.compressionDefault, + Device? device, + required Logger logger, + }) async { + throw Exception('Failed to connect to VM service'); + }, + ); + final device = FakeDevice(LaunchResult.failed(), logReader: logReader); + + try { + await driverService.reuseApplication( + Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'), + device, + DebuggingOptions.enabled(BuildInfo.debug), + ); + fail('Expected reuseApplication to fail'); + } on Exception catch (e) { + expect(e.toString(), contains('Failed to connect to VM service')); + } + expect(logReader.isListened, true); + }, + ); } FlutterDriverService setUpDriverService({ @@ -466,8 +510,10 @@ class FakeApplicationPackage extends Fake implements ApplicationPackage { } class FakeDevice extends Fake implements Device { - FakeDevice(this.result, {this.supportsFlutterExit = true}); + FakeDevice(this.result, {this.supportsFlutterExit = true, DeviceLogReader? logReader}) + : _logReader = logReader ?? NoOpDeviceLogReader('test'); + final DeviceLogReader _logReader; LaunchResult result; bool didStopApp = false; bool didUninstallApp = false; @@ -495,7 +541,7 @@ class FakeDevice extends Fake implements Device { Future getLogReader({ ApplicationPackage? app, bool includePastLogs = false, - }) async => NoOpDeviceLogReader('test'); + }) async => _logReader; @override Future startApp( @@ -563,3 +609,25 @@ class FakeDartDevelopmentService extends Fake disposed = true; } } + +class FakeDeviceLogReader implements DeviceLogReader { + final StreamController _logLinesController = StreamController.broadcast(); + bool isListened = false; + + @override + String get name => 'fake_log_reader'; + + @override + Stream get logLines { + isListened = true; + return _logLinesController.stream; + } + + @override + void dispose() { + _logLinesController.close(); + } + + @override + Future provideVmService(FlutterVmService connectedVmService) async {} +} diff --git a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart index 452311ebb9086..21aa7c7090044 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart @@ -206,9 +206,13 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { UpdateFSReport report = UpdateFSReport(success: true, invalidatedSourcesCount: 1); Exception? reportError; Exception? runColdError; + Exception? connectError; int runHotCode = 0; int runColdCode = 0; + @override + Duration logFlushDelay = Duration.zero; + @override ResidentCompiler? generator; @@ -266,7 +270,11 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { required DebuggingOptions debuggingOptions, int? hostVmServicePort, bool? ipv6 = false, - }) async {} + }) async { + if (connectError != null) { + throw connectError!; + } + } @override Future updateDevFS({ diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 236ea20b5566b..1564f472becb1 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -2275,6 +2275,20 @@ flutter: }, ); + testUsingContext( + 'ResidentRunner delays on connection failure to allow logs to flush', + () => testbed.run(() async { + flutterDevice.connectError = Exception('Failed to connect'); + flutterDevice.logFlushDelay = const Duration(milliseconds: 100); + + final stopwatch = Stopwatch()..start(); + final int result = await residentRunner.attach(); + stopwatch.stop(); + + expect(result, 2); + expect(stopwatch.elapsedMilliseconds, greaterThanOrEqualTo(100)); + }), + ); group('ResidentRunner cached Initial Dill Compilation', () { late TestBed testbed; late FakeFlutterDevice flutterDevice; From 2b9bbc8589390d07f3719f8e0049d28827283cdc Mon Sep 17 00:00:00 2001 From: CodeDoctor Date: Thu, 23 Jul 2026 00:33:51 +0000 Subject: [PATCH 014/147] Fix non primary buttons not being captured on windows (#188394) Closes #166309. Windows only captured mouse input for primary-button drags. As a result, drag gestures using middle, secondary, or X buttons received a pointer remove when leaving the client area. Capture is now enabled for every supported mouse button and released only after the last pressed mouse button is released. Also tested on my universal flutter input demo: https://github.com/CodeDoctorDE/flutter-input-demo/tree/e448c2cf42981d9c8b5186a7e43d1b82a0239619. ~~Currently in draft since I cannot have 2 pull requests open at the same time.~~ ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Matthew Kosarek --- .../shell/platform/windows/flutter_window.cc | 27 ++++--- .../windows/flutter_window_unittests.cc | 74 +++++++++++++++++++ 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_window.cc b/engine/src/flutter/shell/platform/windows/flutter_window.cc index 2b778ddba021e..24398ee3cef42 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window.cc @@ -735,14 +735,11 @@ FlutterWindow::HandleMessage(UINT const message, break; } - if (message == WM_LBUTTONDOWN) { - // Capture the pointer in case the user drags outside the client area. - // In this case, the "mouse leave" event is delayed until the user - // releases the button. It's only activated on left click given that - // it's more common for apps to handle dragging with only the left - // button. - SetCapture(window_handle_); - } + // Capture the pointer in case the user drags outside the client area. + // In this case, the "mouse leave" event is delayed until the user + // releases the button. + SetCapture(window_handle_); + button_pressed = message; if (message == WM_XBUTTONDOWN) { button_pressed = GET_XBUTTON_WPARAM(wparam); @@ -764,9 +761,6 @@ FlutterWindow::HandleMessage(UINT const message, break; } - if (message == WM_LBUTTONUP) { - ReleaseCapture(); - } button_pressed = message; if (message == WM_XBUTTONUP) { button_pressed = GET_XBUTTON_WPARAM(wparam); @@ -775,6 +769,17 @@ FlutterWindow::HandleMessage(UINT const message, y_pos = GET_Y_LPARAM(lparam); flutter_button = ConvertWinButtonToFlutterButton(button_pressed); + // WM_*BUTTONUP messages use wparam to report which buttons remain + // pressed after this event; release capture only after the last mouse + // button is released. WM_*BUTTONDOWN messages already identify the newly + // pressed button via the message itself. + // See: + // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup + if ((wparam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | + MK_XBUTTON2)) == 0) { + ReleaseCapture(); + } + OnPointerUp(static_cast(x_pos), static_cast(y_pos), device_kind, kDefaultPointerDeviceId, flutter_button); break; diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc index e1fbcfe900763..3c3a3f0f488ca 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc @@ -853,6 +853,80 @@ TEST_F(FlutterWindowTest, OnMousePointerDown) { 50); } +TEST_F(FlutterWindowTest, NonPrimaryMouseButtonCapturesPointer) { + MockFlutterWindow win32window(100, 100); + MockWindowBindingHandlerDelegate delegate; + EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber()); + EXPECT_CALL(delegate, OnWindowStateEvent).Times(AnyNumber()); + win32window.SetView(&delegate); + + HWND window_handle = win32window.FlutterWindow::GetWindowHandle(); + ASSERT_NE(window_handle, nullptr); + ReleaseCapture(); + ASSERT_EQ(GetCapture(), nullptr); + + EXPECT_CALL(delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMouseSecondary, 0, 0)) + .Times(1); + win32window.InjectWindowMessage(WM_RBUTTONDOWN, MK_RBUTTON, + MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), window_handle); + + EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMouseSecondary)) + .Times(1); + win32window.InjectWindowMessage(WM_RBUTTONUP, 0, MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), nullptr); +} + +TEST_F(FlutterWindowTest, MouseButtonCaptureReleasedAfterAllButtonsReleased) { + MockFlutterWindow win32window(100, 100); + MockWindowBindingHandlerDelegate delegate; + EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber()); + EXPECT_CALL(delegate, OnWindowStateEvent).Times(AnyNumber()); + win32window.SetView(&delegate); + + HWND window_handle = win32window.FlutterWindow::GetWindowHandle(); + ASSERT_NE(window_handle, nullptr); + ReleaseCapture(); + ASSERT_EQ(GetCapture(), nullptr); + + EXPECT_CALL(delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMouseSecondary, 0, 0)) + .Times(1); + win32window.InjectWindowMessage(WM_RBUTTONDOWN, MK_RBUTTON, + MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), window_handle); + + EXPECT_CALL(delegate, + OnPointerDown(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMousePrimary, 0, 0)) + .Times(1); + win32window.InjectWindowMessage(WM_LBUTTONDOWN, MK_LBUTTON | MK_RBUTTON, + MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), window_handle); + + EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMouseSecondary)) + .Times(1); + win32window.InjectWindowMessage(WM_RBUTTONUP, MK_LBUTTON, MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), window_handle); + + EXPECT_CALL(delegate, OnPointerUp(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMousePrimary)) + .Times(1); + win32window.InjectWindowMessage(WM_LBUTTONUP, 0, MAKELPARAM(10, 10)); + EXPECT_EQ(GetCapture(), nullptr); +} + TEST_F(FlutterWindowTest, OnTouchPointerDown) { auto mock_proc_table = std::make_shared(); From e70d250e32c557c85141c6cd978206aeee90e31f Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 23 Jul 2026 01:01:08 +0000 Subject: [PATCH 015/147] [flutter_tools] Bound Spotlight mdfind execution with timeout on macOS (#189461) ## Description On macOS, `AndroidStudio.allInstalled()` calls `mdfind` via `runSync` to locate Android Studio installations outside standard locations. When Spotlight (`mds_stores`/`mdworker`) is unresponsive or re-indexing, `mdfind` hangs indefinitely and blocks `flutter doctor` and `flutter daemon`. This PR wraps `mdfind` in a shell execution with a 3-second process timeout and killer subshell cleanup to prevent `flutter doctor` and `flutter daemon` from hanging indefinitely. If the query times out or fails, `_allMacOS` gracefully falls back to standard candidate paths. Fixes https://github.com/flutter/flutter/issues/189177 ## Tests - Added `installation detection on MacOS gracefully handles unresponsive Spotlight query (issue #189177)` unit test in `packages/flutter_tools/test/general.shard/android/android_studio_test.dart`. --- .../lib/src/android/android_studio.dart | 19 ++++++++++-- .../android/android_studio_test.dart | 29 ++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart index 312384ffb683d..5d5ce6461f1cf 100644 --- a/packages/flutter_tools/lib/src/android/android_studio.dart +++ b/packages/flutter_tools/lib/src/android/android_studio.dart @@ -5,6 +5,8 @@ /// @docImport 'java.dart'; library; +import 'package:meta/meta.dart'; + import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; @@ -15,6 +17,10 @@ import '../convert.dart'; import '../globals.dart' as globals; import '../ios/plist_parser.dart'; +@visibleForTesting +const String kSpotlightMdfindCommand = + 'mdfind \'kMDItemCFBundleIdentifier="com.google.android.studio*"\' & pid=\$!; (sleep 3; kill -9 \$pid 2>/dev/null) & killer=\$!; wait \$pid 2>/dev/null; status=\$?; kill \$killer 2>/dev/null; exit \$status'; + const _androidStudioTitle = 'Android Studio'; const _androidStudioId = 'AndroidStudio'; const _androidStudioPreviewTitle = 'Android Studio Preview'; @@ -322,13 +328,22 @@ class AndroidStudio { } // Query Spotlight for unexpected installation locations. + // Spotlight (mds_stores/mdworker) can become unresponsive or hang during heavy indexing on macOS. + // Wrap mdfind in a shell execution with a 3-second timeout to prevent flutter doctor + // and flutter daemon from hanging indefinitely (https://github.com/flutter/flutter/issues/189177). var spotlightQueryResult = ''; try { final ProcessResult spotlightResult = globals.processManager.runSync([ - 'mdfind', + 'sh', + '-c', // com.google.android.studio, com.google.android.studio-EAP - 'kMDItemCFBundleIdentifier="com.google.android.studio*"', + kSpotlightMdfindCommand, ]); + if (spotlightResult.exitCode != 0) { + globals.printTrace( + 'Spotlight mdfind query failed or timed out with exit code ${spotlightResult.exitCode}', + ); + } spotlightQueryResult = spotlightResult.stdout as String; } on ProcessException { // The Spotlight query is a nice-to-have, continue checking known installation locations. diff --git a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart index 020727e1e7783..e6c1ac58dabf4 100644 --- a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart @@ -478,10 +478,7 @@ void main() { // Spotlight finds the one known and two random installations. processManager.addCommands([ FakeCommand( - command: const [ - 'mdfind', - 'kMDItemCFBundleIdentifier="com.google.android.studio*"', - ], + command: const ['sh', '-c', kSpotlightMdfindCommand], stdout: '$randomLocation1\n$randomLocation2\n$studioInApplication', ), FakeCommand( @@ -519,6 +516,25 @@ void main() { }, ); + testUsingContext( + 'installation detection on MacOS gracefully handles unresponsive Spotlight query (issue #189177)', + () { + processManager.addCommands([ + const FakeCommand(command: ['sh', '-c', kSpotlightMdfindCommand], exitCode: 137), + ]); + + expect(AndroidStudio.allInstalled(), isEmpty); + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + FileSystem: () => fileSystem, + FileSystemUtils: () => fsUtils, + ProcessManager: () => processManager, + Platform: () => platform, + PlistParser: () => plistUtils, + }, + ); + testUsingContext( 'finds latest valid install', () { @@ -828,10 +844,7 @@ void main() { processManager.addCommands([ FakeCommand( - command: const [ - 'mdfind', - 'kMDItemCFBundleIdentifier="com.google.android.studio*"', - ], + command: const ['sh', '-c', kSpotlightMdfindCommand], stdout: extractedDownloadZip, ), FakeCommand(command: [studioInApplicationJavaBinary, '-version']), From fd81b3a9d65738fb68abc2339d0ed635dabed021 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 23 Jul 2026 01:59:27 +0000 Subject: [PATCH 016/147] [examples] Use super parameters in missed spots (#186194) Work towards https://github.com/dart-lang/sdk/issues/59226 The analyzer will start reporting cases where a super parameter could be used, and the type of the supar parameter is tightened in the subclass constructor. This is still a perfectly valid super parameter use case. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/material/selectable_region/selectable_region.0.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/api/lib/material/selectable_region/selectable_region.0.dart b/examples/api/lib/material/selectable_region/selectable_region.0.dart index eb775d8acae56..f79d89bc1360b 100644 --- a/examples/api/lib/material/selectable_region/selectable_region.0.dart +++ b/examples/api/lib/material/selectable_region/selectable_region.0.dart @@ -54,8 +54,10 @@ class MySelectableAdapter extends StatelessWidget { } class _SelectableAdapter extends SingleChildRenderObjectWidget { - const _SelectableAdapter({required this.registrar, required Widget child}) - : super(child: child); + const _SelectableAdapter({ + required this.registrar, + required Widget super.child, + }); final SelectionRegistrar registrar; From ea1bc5dc4f6fedce69957417b6907e45615146e2 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 02:06:38 +0000 Subject: [PATCH 017/147] Roll Skia from 5e183e5aeac5 to 3424966b8a2b (33 revisions) (#189890) Roll Skia from 5e183e5aeac5 to 3424966b8a2b (33 revisions) https://skia.googlesource.com/skia.git/+log/5e183e5aeac5..3424966b8a2b 2026-07-22 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll Dawn from 4527b42f1a95 to 63af03bd26e2 (8 revisions) 2026-07-22 michaelludwig@google.com Revert "[graphite] Trace more DrawPass details" 2026-07-22 robertphillips@google.com Suppress failure cases for new TestManyStopLinearHardstopsGanesh test 2026-07-22 arthursonzogni@chromium.org Disable partition_alloc on Mac/iOS when using Xcode clang 2026-07-22 egdaniel@google.com [ganesh] Fully clear initial stencil bits when stencil clear is a draw 2026-07-22 michaelludwig@google.com [graphite] Only use CompressedPaintersOrder to stop layer search 2026-07-22 sergiog@microsoft.com [rust bmp] Move rust bmp out of experimental 2026-07-22 thomsmit@google.com [graphite] SparseStrips SIMD coverage improvements 2026-07-22 egdaniel@google.com Make sure partition alloc is disabled on all clang mac names. 2026-07-22 michaelludwig@google.com [viewer] Avoid fLastImage polluting Graphite caches 2026-07-22 robertphillips@google.com [ganesh] Skip resolve/mipmap step on flush failure 2026-07-22 michaelludwig@google.com [graphite] Trace more DrawPass details 2026-07-22 helmut@januschka.com [ganesh] Add many-stop analytic gradient colorizer 2026-07-22 kjlubick@google.com Fix ganesh_native_vulkan target 2026-07-22 michaelludwig@google.com [skif] Request expected child output for Magnifier 2026-07-22 michaelludwig@google.com [tracing] Add TRACE_COUNTER1_ALWAYS variant 2026-07-22 jiawei.shao@intel.com Compare the return value of `GetLimits()` with `wgpu::Status::Success` 2026-07-22 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 218432abf71f to a5843ef093d3 (2 revisions) 2026-07-22 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-22 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-22 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 80d793462953 to 3d3c181bdcf2 (7 revisions) 2026-07-22 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from b8df33bbc301 to ed73bb996e6d (15 revisions) 2026-07-22 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 7c60d39b65ee to 4527b42f1a95 (3 revisions) 2026-07-22 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-22 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-22 thomsmit@google.com [ganesh][gl] ARM FBO query workaround 2026-07-21 michaelludwig@google.com [tracing] Remove TRACE_COUNTER2; implement counters in SkPerfettoTrace 2026-07-21 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll ANGLE from d6247827bfd0 to 80d793462953 (6 revisions) 2026-07-21 michaelludwig@google.com [graphite] Add BoundsFlags and adjust test result names 2026-07-21 kjlubick@google.com Add sanitization proc to SkTypeface::MakeDeserialize 2026-07-21 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from f7e4835de105 to 218432abf71f (2 revisions) 2026-07-21 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll Dawn from 1b6954523e35 to 7c60d39b65ee (6 revisions) 2026-07-21 fmalita@google.com Deprecate SkPath::updateBoundsCache If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 ... --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f4f51fcf34a3e..c39ff4bf6b034 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '5e183e5aeac5ed96b403e54335ece52a42f630ac', + 'skia_revision': '3424966b8a2b1e96427f3eab2bd069954ff745e7', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 2dcbfd49ea1484ad016bb70355c015ba03aa6995 Mon Sep 17 00:00:00 2001 From: Mohellebi Abdessalem <116356835+AbdeMohlbi@users.noreply.github.com> Date: Thu, 23 Jul 2026 05:27:36 +0000 Subject: [PATCH 018/147] Remove outdated logs that were added to track #172636 (#189282) removes the prints that were used to track https://github.com/flutter/flutter/issues/172636 the PR that added the prints https://github.com/flutter/flutter/pull/174073 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Ben Konyi --- .../debug_adapter/test_client.dart | 23 ++----------------- .../debug_adapter/test_support.dart | 6 +---- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart index fbd6cf8c199b7..04776a23e6fef 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(bkonyi): remove and cleanup prints once https://github.com/flutter/flutter/issues/172636 -// is resolved. -// ignore_for_file: avoid_print - import 'dart:async'; import 'package:dds/dap.dart'; @@ -133,7 +129,6 @@ class DapTestClient { bool? supportsRunInTerminalRequest, bool? supportsProgressReporting, }) async { - print('DapTestClient.initialize: wait for responses'); final List responses = await Future.wait(>[ event('initialized'), sendRequest( @@ -145,7 +140,6 @@ class DapTestClient { ), sendRequest(SetExceptionBreakpointsArguments(filters: [exceptionPauseMode])), ]); - print('DapTestClient.initialize: got responses, sending config done'); await sendRequest(ConfigurationDoneArguments()); return responses[1] as Response; // Return the initialize response. } @@ -267,12 +261,8 @@ class DapTestClient { Future Function()? launch, }) { return Future.wait(>[ - initialize( - exceptionPauseMode: exceptionPauseMode, - ).then((_) => print('DapTestClient.initialize: completed')), - (launch?.call() ?? this.launch(program: program, cwd: cwd)).then( - (_) => print('DapTestClient.launch: completed'), - ), + initialize(exceptionPauseMode: exceptionPauseMode), + launch?.call() ?? this.launch(program: program, cwd: cwd), ], eagerError: true); } @@ -408,20 +398,11 @@ extension DapTestClientExtension on DapTestClient { final Future>> testNotificationEventsFuture = testNotificationEvents .toList(); - print('DapTestClient.start: started'); if (start != null) { await start(); } else { await this.start(program: program, cwd: cwd, launch: launch); } - print('DapTestClient.start: completed'); - - unawaited(outputEventsFuture.then((_) => print('DapTestClient.outputEventsFuture: completed'))); - unawaited( - testNotificationEventsFuture.then( - (_) => print('DapTestClient.testNotificationEventsFuture: completed'), - ), - ); return TestEvents( output: await outputEventsFuture, diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart index c176761ab8b13..84ac28524032e 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_support.dart @@ -29,11 +29,7 @@ final useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true'; /// This is useful for debugging locally or on the bots and will include both /// DAP traffic (between the test DAP client and the DAP server) and the VM /// Service traffic (wrapped in a custom 'dart.log' event). -final bool verboseLogging = - Platform.environment['DAP_TEST_VERBOSE'] == 'true' || - // Enable verbose logging on CI bots. - // TODO(bkonyi): remove this once https://github.com/flutter/flutter/issues/172636 is resolved. - Platform.environment.containsKey('SWARMING_TASK_ID'); +final bool verboseLogging = Platform.environment['DAP_TEST_VERBOSE'] == 'true'; const endOfErrorOutputMarker = '════════════════════════════════════════════════════════════════════════════════'; From 2ce938cfdf9782275ad8d5073ce8ab2fc2d7a0ab Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 05:28:55 +0000 Subject: [PATCH 019/147] Roll Fuchsia Linux SDK from GswhlPRO-D1qSNclx... to 9org0yL3yZkp80x5S... (#189898) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jsimmons@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c39ff4bf6b034..d7de46ed9f813 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'GswhlPRO-D1qSNclxUWiXunqJcd3VqLBeNJNGL29QvwC' + 'version': '9org0yL3yZkp80x5SQQhN-0gAbPYRnhYOzWU3luJdE4C' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From e69e703dce29fda392f8024109c1961306cb365a Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 06:55:22 +0000 Subject: [PATCH 020/147] Roll Skia from 3424966b8a2b to 1d8bf9270d8c (3 revisions) (#189901) https://skia.googlesource.com/skia.git/+log/3424966b8a2b..1d8bf9270d8c 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from ed73bb996e6d to 7a6f0a35a522 (13 revisions) 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 63af03bd26e2 to 85891e8dcf47 (6 revisions) 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from a5843ef093d3 to b65fca8cfc87 (6 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index d7de46ed9f813..32e5d6de8468e 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '3424966b8a2b1e96427f3eab2bd069954ff745e7', + 'skia_revision': '1d8bf9270d8cda62f9a5ec82d44318983ff18194', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 22fcba1c30a8ddb0bbde396ead16094955000fd3 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Thu, 23 Jul 2026 14:54:02 +0000 Subject: [PATCH 021/147] Move WindowManager outside of WidgetsApp (#188866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a prototype branch for moving `WindowManager` outside of `WidgetsApp`. Our current approach where first window is "blessed" and other windows are anchored inside first window hierarchy is causing multiple issues: - Only the first window has navigator - Secondary windows focus zones are inside primary window focus zone so it is always focused. - Widget inspector only works in primary window In general there is no reason for one window to be special. Normally for a desktop application all windows should be equal. The closest we can get to this is to give each top level window its own Widgets/MaterialApp. While this duplicates some elements that should normally be shared, arguably it behaves better than doing what we do right now. Ideally we'd want to split the App widget into shared and per window parts, but that will be a much bigger change. With this branch the top level Dialog/Regular window have each their own MaterialApp widget and separate focus trees, solving the issues above. `Popup` and `Tooltip` window work and are anchored at proper place in window hierarchy (they should not be top level window). However we might want to consider a convenience Widget to anchoring these instead of using `ViewAnchor` + `View`. Screenshot of example app running in this mode - notice the Debug banner and widget inspector buttons in each window. Screenshot 2026-07-01 at 5 48 13 PM --- .../lib/widgets/windows/window_manager.0.dart | 7 +- .../lib/app/dialog_window_content.dart | 5 +- .../multiple_windows/lib/app/main_window.dart | 2 +- .../lib/app/popup_button.dart | 17 ++- .../lib/app/regular_window_content.dart | 5 +- .../lib/app/tooltip_button.dart | 17 ++- examples/multiple_windows/lib/main.dart | 10 +- packages/flutter/lib/src/widgets/_window.dart | 27 ++-- packages/flutter/lib/src/widgets/app.dart | 6 - packages/flutter/lib/src/widgets/dialog.dart | 118 ------------------ 10 files changed, 50 insertions(+), 164 deletions(-) diff --git a/examples/api/lib/widgets/windows/window_manager.0.dart b/examples/api/lib/widgets/windows/window_manager.0.dart index 71dad55a5f584..e8d0d178d7657 100644 --- a/examples/api/lib/widgets/windows/window_manager.0.dart +++ b/examples/api/lib/widgets/windows/window_manager.0.dart @@ -17,7 +17,12 @@ void main() { ); runWidget( WindowManager( - child: RegularWindow(controller: controller, child: const MainWindow()), + initialWindows: [ + WindowEntry( + controller: controller, + builder: (context) => const MainWindow(), + ), + ], ), ); } on UnsupportedError catch (e) { diff --git a/examples/multiple_windows/lib/app/dialog_window_content.dart b/examples/multiple_windows/lib/app/dialog_window_content.dart index 46935af34067e..a35cc95af7ec9 100644 --- a/examples/multiple_windows/lib/app/dialog_window_content.dart +++ b/examples/multiple_windows/lib/app/dialog_window_content.dart @@ -19,9 +19,8 @@ class DialogWindowContent extends StatelessWidget { Widget build(BuildContext context) { final WindowSettings windowSettings = WindowSettingsAccessor.of(context); - return Overlay.wrap( - alwaysSizeToContent: true, - child: FocusScope( + return MaterialApp( + home: FocusScope( autofocus: true, child: IntrinsicWidth( child: Material( diff --git a/examples/multiple_windows/lib/app/main_window.dart b/examples/multiple_windows/lib/app/main_window.dart index 988406e1dc6bb..d8be3ed5b2cd1 100644 --- a/examples/multiple_windows/lib/app/main_window.dart +++ b/examples/multiple_windows/lib/app/main_window.dart @@ -94,7 +94,7 @@ class _WindowsTable extends StatelessWidget { } List _buildRows(WindowRegistry windowRegistry, BuildContext context) { - final List rows = [_buildRow(mainWindow, context)]; + final List rows = []; for (final WindowEntry entry in windowRegistry.windows) { final BaseWindowController controller = entry.controller; rows.add(_buildRow(controller, context)); diff --git a/examples/multiple_windows/lib/app/popup_button.dart b/examples/multiple_windows/lib/app/popup_button.dart index 3f18cec5da5ad..343030db338a2 100644 --- a/examples/multiple_windows/lib/app/popup_button.dart +++ b/examples/multiple_windows/lib/app/popup_button.dart @@ -32,7 +32,7 @@ class _PopupButtonState extends State { super.dispose(); } - void _onPressed(WindowRegistry windowRegistry, WindowSettings windowSettings) { + void _onPressed(WindowSettings windowSettings) { // Toggle popup visibility. if (_popupWindowEntry != null) { _popupWindowEntry!.controller.destroy(); @@ -50,7 +50,6 @@ class _PopupButtonState extends State { positioner: windowSettings.positioner, delegate: _PopupWindowControllerDelegate( onDestroyed: () { - windowRegistry.unregister(entry); tracker.dispose(); if (mounted) { setState(() { @@ -66,7 +65,6 @@ class _PopupButtonState extends State { controller: controller, builder: (BuildContext context) => PopupWindowContent(controller: controller), ); - windowRegistry.register(entry); tracker.onGlobalRectChange = (rect) { controller.updatePosition(anchorRect: rect); }; @@ -79,13 +77,20 @@ class _PopupButtonState extends State { @override Widget build(BuildContext context) { - final WindowRegistry windowManager = WindowRegistry.of(context); final WindowSettings windowSettings = WindowSettingsAccessor.of(context); return OutlinedButton( key: _popupButtonKey, - onPressed: () => _onPressed(windowManager, windowSettings), - child: Text(_popupWindowEntry != null ? 'Hide Popup' : 'Show Popup'), + onPressed: () => _onPressed(windowSettings), + child: ViewAnchor( + view: _popupWindowEntry != null + ? View( + view: _popupWindowEntry!.controller.rootView, + child: Builder(builder: _popupWindowEntry!.builder), + ) + : null, + child: Text(_popupWindowEntry != null ? 'Hide Popup' : 'Show Popup'), + ), ); } } diff --git a/examples/multiple_windows/lib/app/regular_window_content.dart b/examples/multiple_windows/lib/app/regular_window_content.dart index 7c71ec237fdad..df9498bc7e872 100644 --- a/examples/multiple_windows/lib/app/regular_window_content.dart +++ b/examples/multiple_windows/lib/app/regular_window_content.dart @@ -49,9 +49,8 @@ class _RegularWindowContentState extends State { final double dpr = MediaQuery.of(context).devicePixelRatio; final Size windowSize = WindowScope.contentSizeOf(context); - return Overlay.wrap( - alwaysSizeToContent: true, - child: IntrinsicWidth( + return MaterialApp( + home: IntrinsicWidth( child: Material( child: Column( mainAxisSize: .min, diff --git a/examples/multiple_windows/lib/app/tooltip_button.dart b/examples/multiple_windows/lib/app/tooltip_button.dart index d2e5fa3674472..d7520a5f04f3b 100644 --- a/examples/multiple_windows/lib/app/tooltip_button.dart +++ b/examples/multiple_windows/lib/app/tooltip_button.dart @@ -32,7 +32,7 @@ class _TooltipButtonState extends State { super.dispose(); } - void _onPressed(WindowRegistry windowRegistry, WindowSettings windowSettings) { + void _onPressed(WindowSettings windowSettings) { // Toggle tooltip visibility. if (_tooltipEntry != null) { _tooltipEntry!.controller.destroy(); @@ -50,7 +50,6 @@ class _TooltipButtonState extends State { positioner: windowSettings.positioner, delegate: _TooltipWindowControllerDelegate( onDestroyed: () { - windowRegistry.unregister(entry); tracker.dispose(); if (mounted) { setState(() { @@ -66,7 +65,6 @@ class _TooltipButtonState extends State { controller: controller, builder: (BuildContext context) => TooltipWindowContent(controller: controller), ); - windowRegistry.register(entry); tracker.onGlobalRectChange = (rect) { controller.updatePosition(anchorRect: rect); }; @@ -79,13 +77,20 @@ class _TooltipButtonState extends State { @override Widget build(BuildContext context) { - final WindowRegistry windowManager = WindowRegistry.of(context); final WindowSettings windowSettings = WindowSettingsAccessor.of(context); return OutlinedButton( key: _tooltipButtonKey, - onPressed: () => _onPressed(windowManager, windowSettings), - child: Text(_tooltipEntry != null ? 'Hide Tooltip' : 'Show Tooltip'), + onPressed: () => _onPressed(windowSettings), + child: ViewAnchor( + view: _tooltipEntry != null + ? View( + view: _tooltipEntry!.controller.rootView, + child: Builder(builder: _tooltipEntry!.builder), + ) + : null, + child: Text(_tooltipEntry != null ? 'Hide Tooltip' : 'Show Tooltip'), + ), ); } } diff --git a/examples/multiple_windows/lib/main.dart b/examples/multiple_windows/lib/main.dart index d62ecfe21e9b9..1bebab3e4208f 100644 --- a/examples/multiple_windows/lib/main.dart +++ b/examples/multiple_windows/lib/main.dart @@ -52,9 +52,13 @@ class _MultiWindowAppState extends State { Widget build(BuildContext context) { return WindowSettingsAccessor( windowSettings: settings, - child: RegularWindow( - controller: controller, - child: MaterialApp(home: MainWindow(controller: controller)), + child: WindowManager( + initialWindows: [ + WindowEntry( + controller: controller, + builder: (context) => MaterialApp(home: MainWindow(controller: controller)), + ), + ], ), ); } diff --git a/packages/flutter/lib/src/widgets/_window.dart b/packages/flutter/lib/src/widgets/_window.dart index 89fc13045eeb3..9cccaf08f6016 100644 --- a/packages/flutter/lib/src/widgets/_window.dart +++ b/packages/flutter/lib/src/widgets/_window.dart @@ -2567,10 +2567,10 @@ class WindowManager extends StatefulWidget { /// /// {@macro flutter.widgets.windowing.experimental} @internal - const WindowManager({super.key, required this.child}); + const WindowManager({super.key, required this.initialWindows}); - /// The child widget of the window manager. - final Widget child; + /// The initial windows to be registered and managed by this window manager. + final List initialWindows; @override State createState() => _WindowManagerState(); @@ -2580,11 +2580,13 @@ class _WindowManagerState extends State { final WindowRegistry _registry = WindowRegistry(); @override - Widget build(BuildContext context) { - if (!isWindowingEnabled) { - return widget.child; - } + void initState() { + super.initState(); + widget.initialWindows.forEach(_registry.register); + } + @override + Widget build(BuildContext context) { return _WindowRegistryScope( registry: _registry, child: ListenableBuilder( @@ -2615,17 +2617,8 @@ class _WindowManagerState extends State { }; }).toList(); - final FlutterView? view = View.maybeOf(context); - if (view == null) { - return ViewCollection(views: subViews); - } - - return ViewAnchor( - view: subViews.isNotEmpty ? ViewCollection(views: subViews) : null, - child: child!, - ); + return ViewCollection(views: subViews); }, - child: widget.child, ), ); } diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index c3c670626f78e..948d3f599884a 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -15,8 +15,6 @@ import 'dart:collection' show HashMap; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import '../foundation/_features.dart' show isWindowingEnabled; -import '_window.dart' show WindowManager; import 'actions.dart'; import 'banner.dart'; @@ -1730,10 +1728,6 @@ class _WidgetsAppState extends State with WidgetsBindingObserver { result = routing!; } - if (isWindowingEnabled) { - result = WindowManager(child: result); - } - if (widget.textStyle != null) { result = DefaultTextStyle(style: widget.textStyle!, child: result); } diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 7a2e3cb3b3127..bd05016932872 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -2,24 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; - -import '../foundation/_features.dart' show isWindowingEnabled; - -import '_window.dart' - show - BaseWindowController, - DialogWindowController, - DialogWindowControllerDelegate, - WindowEntry, - WindowRegistry, - WindowScope; - import 'basic.dart'; import 'debug.dart'; import 'framework.dart'; import 'navigator.dart'; -import 'overlay.dart'; import 'routes.dart'; /// A builder for a route that takes the build context and the widget intended @@ -78,27 +64,6 @@ Future showRawDialog({ final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator); - final WindowRegistry? windowRegistry = WindowRegistry.maybeOf(context); - if (windowRegistry != null && isWindowingEnabled) { - try { - final Size? parentSize = WindowScope.maybeContentSizeOf(context); - return navigator.push( - _DialogWindowRoute( - builder: builder, - parentController: WindowScope.maybeOf(context), - context: context, - settings: routeSettings, - size: fullscreenDialog ? parentSize : null, - ), - ); - } on UnsupportedError catch (error, stacktrace) { - // Fallback to normal dialog route if windowing is not supported. - FlutterError.reportError( - FlutterErrorDetails(exception: error, library: 'widgets library', stack: stacktrace), - ); - } - } - final Route route = routeBuilder?.call(context, builder) ?? RawDialogRoute( @@ -114,86 +79,3 @@ Future showRawDialog({ return navigator.push(route); } - -class _DialogWindowDelegate extends DialogWindowControllerDelegate { - _DialogWindowDelegate(this.route); - - final _DialogWindowRoute route; - - @override - void onWindowCloseRequested(DialogWindowController controller) { - route.navigator?.pop(); - } -} - -class _DialogWindowRoute extends Route { - _DialogWindowRoute({ - required this.builder, - required this.parentController, - required BuildContext context, - super.settings, - Size? size, - }) : _registry = WindowRegistry.maybeOf(context) { - _controller = size != null - ? DialogWindowController( - parent: parentController, - title: 'Dialog', - delegate: _DialogWindowDelegate(this), - size: size, - ) - : DialogWindowController.sizedToContent( - parent: parentController, - title: 'Dialog', - delegate: _DialogWindowDelegate(this), - ); - } - - final WidgetBuilder builder; - final BaseWindowController? parentController; - final WindowRegistry? _registry; - DialogWindowController? _controller; - WindowEntry? _entry; - late final List _overlayEntries; - - @override - List get overlayEntries => _overlayEntries; - - @override - void install() { - super.install(); - - // Create a minimal transparent overlay entry to satisfy Navigator requirements. - // The actual dialog content is rendered through ViewAnchor, not through this overlay. - _overlayEntries = [ - OverlayEntry(builder: (BuildContext context) => const SizedBox.shrink()), - ]; - - final NavigatorState? nav = navigator; - final BuildContext? routeContext = nav?.context; - if (routeContext != null && nav != null) { - _entry = WindowEntry(controller: _controller!, builder: builder); - _registry?.register(_entry!); - } - } - - @override - TickerFuture didPush() { - return super.didPush(); - } - - @override - bool didPop(T? result) { - if (_entry != null) { - _registry?.unregister(_entry!); - } - _controller?.destroy(); - return super.didPop(result); - } - - @override - void dispose() { - _controller?.dispose(); - _controller = null; - super.dispose(); - } -} From c3f6236f61f5650c315cd21da8bc58bb66f77a5f Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 23 Jul 2026 15:44:18 +0000 Subject: [PATCH 022/147] Disable execution order shuffling for the flutter_tools upgrade_test suite (#189920) This test is failing when run with today's randomized order seed. See https://github.com/flutter/flutter/issues/189919 --- .../test/commands.shard/permeable/upgrade_test.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index 82a93250150b2..d3340d53b176b 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(team-tools): Remove when the execution order dependency is resolved. +// See https://github.com/flutter/flutter/issues/189919 +@Tags(['no-shuffle']) +library; + import 'package:args/command_runner.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; From 2deea746d398dd89812a59a58565776f0702e6b7 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 23 Jul 2026 10:27:38 -0700 Subject: [PATCH 023/147] forceNdkDownload should skip configuring cmake when ndk-build is used (#187201) Fixes: #175968 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. --------- Co-authored-by: Reid Baker --- .../src/main/kotlin/FlutterPluginUtils.kt | 4 +- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 85 ++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index f69259cd66d9b..50779e6d0b8d0 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -795,8 +795,10 @@ object FlutterPluginUtils { // If the project is already configuring a native build, we don't need to do anything. val gradleProjectAndroidExtension = getLegacyAndroidExtension(gradleProject) + val externalNativeBuild = gradleProjectAndroidExtension.externalNativeBuild val forcingNotRequired: Boolean = - gradleProjectAndroidExtension.externalNativeBuild.cmake.path != null + externalNativeBuild?.cmake?.path != null || + externalNativeBuild?.ndkBuild?.path != null if (forcingNotRequired) { return } diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index c3b736726d692..21fc5d8786f9d 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -1875,6 +1875,7 @@ class FlutterPluginUtilsTest { fakeCmakeFile.createNewFile() val project = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { @@ -1882,9 +1883,15 @@ class FlutterPluginUtilsTest { .findByType(BaseExtension::class.java)!! .externalNativeBuild.cmake } returns mockCmakeOptions + every { + project.extensions + .findByType(BaseExtension::class.java)!! + .externalNativeBuild.ndkBuild + } returns mockNdkBuildOptions every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig every { mockCmakeOptions.path } returns fakeCmakeFile + every { mockNdkBuildOptions.path } returns null FlutterPluginUtils.forceNdkDownload(project, "ignored") @@ -1896,6 +1903,44 @@ class FlutterPluginUtilsTest { } @Test + fun `forceNdkDownload skips projects which are already configuring an ndk-build`( + @TempDir tempDir: Path + ) { + val fakeAndroidMkFile = tempDir.resolve("Android.mk").toFile() + fakeAndroidMkFile.createNewFile() + val project = mockk() + val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() + val mockDefaultConfig = mockk() + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null + every { + project.extensions + .findByType(BaseExtension::class.java)!! + .externalNativeBuild.cmake + } returns mockCmakeOptions + every { + project.extensions + .findByType(BaseExtension::class.java)!! + .externalNativeBuild.ndkBuild + } returns mockNdkBuildOptions + every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig + + every { mockCmakeOptions.path } returns null + every { mockNdkBuildOptions.path } returns fakeAndroidMkFile + + FlutterPluginUtils.forceNdkDownload(project, "ignored") + + verify(exactly = 1) { + mockCmakeOptions.path + } + verify(exactly = 1) { + mockNdkBuildOptions.path + } + verify(exactly = 0) { mockCmakeOptions.path(any()) } + verify(exactly = 0) { mockCmakeOptions.buildStagingDirectory(any()) } + verify { mockDefaultConfig wasNot called } + } + fun `forceNdkDownload installs a missing ndk when tool properties are provided`( @TempDir tempDir: Path ) { @@ -1906,10 +1951,13 @@ class FlutterPluginUtilsTest { val mockExecResult = mockk() val mockExecOperations = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -1954,10 +2002,13 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -1981,6 +2032,7 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() @@ -1989,6 +2041,8 @@ class FlutterPluginUtilsTest { every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } answers { cmakePath } @@ -2032,11 +2086,14 @@ class FlutterPluginUtilsTest { val mockExecResult = mockk() val mockExecOperations = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() var configuredNdkVersion = "26.3.11579264" every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } answers { configuredNdkVersion } every { mockCmakeOptions.path } returns null @@ -2090,6 +2147,7 @@ class FlutterPluginUtilsTest { val mockExecResult = mockk() val mockExecOperations = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() val mockApplicationExtension = mockk() @@ -2099,6 +2157,8 @@ class FlutterPluginUtilsTest { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } answers { throw AssertionError( @@ -2150,11 +2210,14 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -2175,12 +2238,15 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() val mockApplicationExtension = mockk() every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } answers { throw AssertionError("legacy ndkVersion should not be read when ApplicationExtension is available") @@ -2208,10 +2274,13 @@ class FlutterPluginUtilsTest { val mockExecResult = mockk() val mockExecOperations = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockBaseExtension = mockk() every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -2262,6 +2331,7 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() @@ -2269,6 +2339,8 @@ class FlutterPluginUtilsTest { every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -2310,6 +2382,7 @@ class FlutterPluginUtilsTest { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() @@ -2317,6 +2390,8 @@ class FlutterPluginUtilsTest { every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions + every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions + every { mockNdkBuildOptions.path } returns null every { mockBaseExtension.defaultConfig } returns mockDefaultConfig every { mockBaseExtension.ndkVersion } returns "29.0.13846066" every { mockCmakeOptions.path } returns null @@ -2358,6 +2433,7 @@ class FlutterPluginUtilsTest { fun `forceNdkDownload sets externalNativeBuild properties`() { val project = mockk() val mockCmakeOptions = mockk() + val mockNdkBuildOptions = mockk() val mockDefaultConfig = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() @@ -2370,17 +2446,24 @@ class FlutterPluginUtilsTest { .findByType(BaseExtension::class.java)!! .externalNativeBuild.cmake } returns mockCmakeOptions + every { + project.extensions + .findByType(BaseExtension::class.java)!! + .externalNativeBuild.ndkBuild + } returns mockNdkBuildOptions every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig val basePath = "/base/path" val fakeBuildPath = "/randomapp/build/app/" every { mockCmakeOptions.path } returns null + every { mockNdkBuildOptions.path } returns null every { mockCmakeOptions.path(any()) } returns Unit every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit every { project.layout.buildDirectory } returns mockDirectoryProperty every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty every { mockDirectoryProperty.get() } returns mockDirectory - every { mockDirectory.asFile.path } returns fakeBuildPath + val realFile = File(fakeBuildPath) + every { mockDirectory.asFile } returns realFile val mockBuildType = mockk() every { From 2eb7d318d696ed9289f058496aa8cddfcd24b82e Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Thu, 23 Jul 2026 11:21:23 -0700 Subject: [PATCH 024/147] Bump devtools_shared to 13.1.0 (#189507) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter_tools/pubspec.yaml | 7 ++++--- pubspec.lock | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index c3e8fb41f8e93..40a10800ebc9e 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: analyzer: 10.1.0 archive: 3.6.1 args: 2.7.0 - dds: 5.3.0 + dds: 5.4.0 dwds: 27.1.2 code_builder: 4.11.1 collection: 1.19.1 @@ -85,7 +85,7 @@ dependencies: csslib: 1.0.2 dap: 1.4.0 dds_service_extensions: 2.1.0 - devtools_shared: 12.1.0 + devtools_shared: 13.1.0 dtd: 4.0.0 extension_discovery: 2.1.0 fixnum: 1.1.1 @@ -127,4 +127,5 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 8tnhd7 + +# PUBSPEC CHECKSUM: qi12j diff --git a/pubspec.lock b/pubspec.lock index a47233dcd6897..b79d2b4411197 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -263,10 +263,10 @@ packages: dependency: transitive description: name: dds - sha256: "6673c5b29e502fd44dbf5836685e7c7c16b11c0fa7f91ef241e0cbf0638c8794" + sha256: f8e875dcd7b5e5073a7bfe54095144c70487f18e2f8e47fa51c09dd67eccfb0c url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.4.0" dds_service_extensions: dependency: transitive description: @@ -279,10 +279,10 @@ packages: dependency: transitive description: name: devtools_shared - sha256: "2daf7a9fba6a470668b26ecbd04200f7bf992aad81a2c31d12457c7791419dea" + sha256: "13df2c17d5f21dd7c92d08145386d45e1153364c04e531e994afb0ee7b67554b" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "13.1.0" dtd: dependency: transitive description: From 0900317224709e921c24401dee2df970d5a2fb0c Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 23 Jul 2026 13:24:04 -0700 Subject: [PATCH 025/147] [flutter_tools] Invalidate WebEntrypointTarget when plugin set changes (#189460) Add `.dart_tool/package_config.json`, `pubspec.yaml`, and `.flutter-plugins-dependencies` (optional) to `WebEntrypointTarget.inputs`, and `{BUILD_DIR}/web_plugin_registrant.dart` to `WebEntrypointTarget.outputs`. Previously, `WebEntrypointTarget` only declared `{BUILD_DIR}/main.dart` in `outputs` and its own source file in `inputs`. When plugins were added or removed between consecutive `flutter build web` invocations, `FlutterBuildSystem` evaluated `WebEntrypointTarget` as up-to-date, skipping target execution and leaving `web_plugin_registrant.dart` stale. Fixes https://github.com/flutter/flutter/issues/189128 --- .../lib/src/build_system/targets/web.dart | 8 +++++- .../build_system/targets/web_test.dart | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) 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 7fdb980d904ed..57775b9065d9d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -54,10 +54,16 @@ class WebEntrypointTarget extends Target { @override List get inputs => const [ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'), + Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config.json'), + Source.pattern('{PROJECT_DIR}/pubspec.yaml'), + Source.pattern('{PROJECT_DIR}/.flutter-plugins-dependencies', optional: true), ]; @override - List get outputs => const [Source.pattern('{BUILD_DIR}/main.dart')]; + List get outputs => const [ + Source.pattern('{BUILD_DIR}/main.dart'), + Source.pattern('{BUILD_DIR}/web_plugin_registrant.dart'), + ]; @override Future build(Environment environment) async { 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 30ef77873b3c3..a51af6e45bfd6 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 @@ -134,6 +134,31 @@ name: foo ), ); + test( + 'WebEntrypointTarget declares package_config.json, pubspec.yaml, and plugin dependencies as inputs', + () => testbed.run(() async { + const target = WebEntrypointTarget(); + expect( + target.inputs, + equals([ + const Source.pattern( + '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart', + ), + const Source.pattern('{WORKSPACE_DIR}/.dart_tool/package_config.json'), + const Source.pattern('{PROJECT_DIR}/pubspec.yaml'), + const Source.pattern('{PROJECT_DIR}/.flutter-plugins-dependencies', optional: true), + ]), + ); + expect( + target.outputs, + equals([ + const Source.pattern('{BUILD_DIR}/main.dart'), + const Source.pattern('{BUILD_DIR}/web_plugin_registrant.dart'), + ]), + ); + }), + ); + test( 'version.json is created after release build', () => testbed.run(() async { From f3ece6366037cc8976acf6d8c87bd7a35a2d9061 Mon Sep 17 00:00:00 2001 From: Chikamatsu Kazuya <43089218+chika3742@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:27:32 +0900 Subject: [PATCH 026/147] Allow building projects lacking Runner.xcworkspace (#186239) Related: #185924 I have ensured that Flutter commands do not throw errors even in projects without `Runner.xcworkspace`, such as those that have migrated to SwiftPM. Some migration processes checked for the existence of a workspace, but these do not read or write to the workspace directory and function correctly even if it is absent. While the exact intent is unclear, it is likely that they were checking whether Cocoapods was included in the project. Also, it was causing an error in `buildXcodeProject` by not passing scheme and BUILD_DIR as arguments when the workspace did not exist . ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter_tools/lib/src/ios/mac.dart | 19 +-- .../lib/src/macos/build_macos.dart | 20 ++- .../src/migrations/lldb_init_migration.dart | 4 - ...package_manager_integration_migration.dart | 3 - .../hermetic/build_ios_test.dart | 69 +++++++++- .../hermetic/build_macos_test.dart | 65 ++++++++- .../migrations/lldb_init_migration_test.dart | 59 +++++---- ...ge_manager_integration_migration_test.dart | 66 ++++++---- .../build_without_xcworkspace_test.dart | 124 ++++++++++++++++++ 9 files changed, 337 insertions(+), 92 deletions(-) create mode 100644 packages/flutter_tools/test/integration.shard/build_without_xcworkspace_test.dart diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 0a6c0cd66fd05..31d03819723a8 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -411,16 +411,17 @@ Future buildXcodeProject({ final Directory? workspacePath = app.project.xcodeWorkspace; if (workspacePath != null) { - buildCommands.addAll([ - '-workspace', - workspacePath.basename, - '-scheme', - scheme, - if (buildAction != - XcodeBuildAction.archive) // dSYM files aren't copied to the archive if BUILD_DIR is set. - 'BUILD_DIR=${globals.fs.path.absolute(buildDirectoryPath)}', - ]); + buildCommands.addAll(['-workspace', workspacePath.basename]); + } else { + buildCommands.addAll(['-project', app.project.xcodeProject.basename]); } + buildCommands.addAll([ + '-scheme', + scheme, + if (buildAction != + XcodeBuildAction.archive) // dSYM files aren't copied to the archive if BUILD_DIR is set. + 'BUILD_DIR=${globals.fs.path.absolute(buildDirectoryPath)}', + ]); // Check if the project contains a watchOS companion app. final bool hasWatchCompanion = await app.project.containsWatchCompanion( diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 832121498b7e6..6c9a957f28da5 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -86,14 +86,19 @@ Future buildMacOS({ SizeAnalyzer? sizeAnalyzer, bool usingCISystem = false, }) async { - final Directory? xcodeWorkspace = flutterProject.macos.xcodeWorkspace; - if (xcodeWorkspace == null) { + final Directory xcodeProject = flutterProject.macos.xcodeProject; + if (!xcodeProject.existsSync()) { throwToolExit( 'No macOS desktop project configured. ' 'See https://flutter.dev/to/add-desktop-support ' 'to learn about adding macOS support to a project.', ); } + + // The .xcworkspace may not exist (e.g. a project using Swift Package Manager + // without CocoaPods). When absent, xcodebuild builds the .xcodeproj directly. + final Directory? xcodeWorkspace = flutterProject.macos.xcodeWorkspace; + const FlutterDarwinPlatform darwinPlatform = .macos; final migrators = [ RemoveMacOSFrameworkLinkAndEmbeddingMigration( @@ -140,8 +145,6 @@ Future buildMacOS({ flutterBuildDir.createSync(recursive: true); } - final Directory xcodeProject = flutterProject.macos.xcodeProject; - // If the standard project exists, specify it to getInfo to handle the case where there are // other Xcode projects in the macos/ directory. Otherwise pass no name, which will work // regardless of the project name so long as there is exactly one project. @@ -280,8 +283,13 @@ Future buildMacOS({ [ '/usr/bin/env', ...xcodebuildCommandArgs, - '-workspace', - xcodeWorkspace.path, + if (xcodeWorkspace != null) ...[ + '-workspace', + xcodeWorkspace.path, + ] else ...[ + '-project', + xcodeProject.path, + ], '-configuration', configuration, '-scheme', diff --git a/packages/flutter_tools/lib/src/migrations/lldb_init_migration.dart b/packages/flutter_tools/lib/src/migrations/lldb_init_migration.dart index 3751808b96ac7..28e0a08c01eaa 100644 --- a/packages/flutter_tools/lib/src/migrations/lldb_init_migration.dart +++ b/packages/flutter_tools/lib/src/migrations/lldb_init_migration.dart @@ -73,10 +73,6 @@ class LLDBInitMigration extends ProjectMigrator { logger.printTrace('Unable to get Xcode project info.'); throw _exceptionMessage(); } - if (_xcodeProject.xcodeWorkspace == null) { - logger.printTrace('Xcode workspace not found.'); - throw _exceptionMessage(); - } final String? scheme = projectInfo.schemeFor(_buildInfo); if (scheme == null) { projectInfo.reportFlavorNotFoundAndExit(); 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 index dc7df4db919ca..1e5c8286982c8 100644 --- 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 @@ -273,9 +273,6 @@ class SwiftPackageManagerIntegrationMigration extends ProjectMigrator { 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(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart index 59229b6dee6d6..d474177da93d0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart @@ -103,13 +103,15 @@ void main() { } // Sets up the minimal mock project files necessary for iOS builds to succeed. - void createMinimalMockProjectFiles() { + void createMinimalMockProjectFiles({bool createWorkspace = true}) { fileSystem .directory(fileSystem.path.join('ios', 'Runner.xcodeproj')) .createSync(recursive: true); - fileSystem - .directory(fileSystem.path.join('ios', 'Runner.xcworkspace')) - .createSync(recursive: true); + if (createWorkspace) { + fileSystem + .directory(fileSystem.path.join('ios', 'Runner.xcworkspace')) + .createSync(recursive: true); + } fileSystem .file(fileSystem.path.join('ios', 'Runner.xcodeproj', 'project.pbxproj')) .createSync(); @@ -167,6 +169,7 @@ void main() { bool verbose = false, bool simulator = false, bool customNaming = false, + bool hasWorkspace = true, bool disablePortPublication = false, String? deviceId, int exitCode = 0, @@ -182,8 +185,13 @@ void main() { if (verbose) 'VERBOSE_SCRIPT_LOGGING=YES' else '-quiet', '-allowProvisioningUpdates', '-allowProvisioningDeviceRegistration', - '-workspace', - if (customNaming) 'RenamedWorkspace.xcworkspace' else 'Runner.xcworkspace', + if (hasWorkspace) ...[ + '-workspace', + if (customNaming) 'RenamedWorkspace.xcworkspace' else 'Runner.xcworkspace', + ] else ...[ + '-project', + if (customNaming) 'RenamedProj.xcodeproj' else 'Runner.xcodeproj', + ], '-scheme', 'Runner', 'BUILD_DIR=/build/ios', @@ -641,6 +649,55 @@ void main() { }, ); + testUsingContext( + 'ios build invokes xcodebuild with -project when there is no .xcworkspace', + () async { + final command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: fileSystem, + logger: logger, + osUtils: FakeOperatingSystemUtils(), + config: FakeConfig(), + platform: FakePlatform(), + fileSystemUtils: FakeFileSystemUtils(), + terminal: FakeTerminal(), + plistParser: FakePlistParser(), + processUtils: FakeProcessUtils(), + processManager: FakeProcessManager.any(), + templateRenderer: FakeTemplateRenderer(), + xcode: FakeXcode(), + artifacts: FakeArtifacts(), + cache: FakeCache(), + flutterVersion: FakeFlutterVersion(), + ); + createMinimalMockProjectFiles(createWorkspace: false); + + processManager.addCommands([ + setUpFakeXcodeBuildHandler( + hasWorkspace: false, + onRun: (_) { + fileSystem + .directory('build/ios/Release-iphoneos/Runner.app') + .createSync(recursive: true); + }, + ), + ...postBuildCommands(), + ]); + + await createTestCommandRunner(command).run(const ['build', 'ios', '--no-pub']); + expect(testLogger.statusText, contains('build/ios/iphoneos/Runner.app')); + }, + overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Pub: ThrowingPub.new, + Platform: () => macosPlatform, + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + Artifacts: () => Artifacts.test(), + }, + ); + testUsingContext( 'ios build invokes xcode build with device ID', () async { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index 561b2a9c6d174..bbc1ae2ede174 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -131,13 +131,15 @@ void main() { } // Sets up the minimal mock project files necessary for macOS builds to succeed. - void createMinimalMockProjectFiles() { + void createMinimalMockProjectFiles({bool createWorkspace = true}) { fileSystem .directory(fileSystem.path.join('macos', 'Runner.xcodeproj')) .createSync(recursive: true); - fileSystem - .directory(fileSystem.path.join('macos', 'Runner.xcworkspace')) - .createSync(recursive: true); + if (createWorkspace) { + fileSystem + .directory(fileSystem.path.join('macos', 'Runner.xcworkspace')) + .createSync(recursive: true); + } createCoreMockProjectFiles(); } @@ -146,6 +148,7 @@ void main() { FakeCommand setUpFakeXcodeBuildHandler( String configuration, { bool verbose = false, + bool hasWorkspace = true, void Function(List command)? onRun, List? additionalCommandArguments, String hostPlatformArch = 'x86_64', @@ -160,8 +163,13 @@ void main() { '/usr/bin/env', 'xcrun', 'xcodebuild', - '-workspace', - flutterProject.macos.xcodeWorkspace!.path, + if (hasWorkspace) ...[ + '-workspace', + flutterProject.macos.xcodeWorkspace!.path, + ] else ...[ + '-project', + flutterProject.macos.xcodeProject.path, + ], '-configuration', configuration, '-scheme', @@ -305,6 +313,51 @@ STDERR STUFF }, ); + testUsingContext( + 'macOS build invokes xcodebuild with -project when there is no .xcworkspace', + () async { + final command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: fileSystem, + logger: logger, + osUtils: FakeOperatingSystemUtils(), + config: FakeConfig(), + platform: FakePlatform(), + fileSystemUtils: FakeFileSystemUtils(), + terminal: FakeTerminal(), + plistParser: FakePlistParser(), + processUtils: FakeProcessUtils(), + processManager: FakeProcessManager.any(), + templateRenderer: FakeTemplateRenderer(), + xcode: FakeXcode(), + artifacts: FakeArtifacts(), + cache: FakeCache(), + flutterVersion: FakeFlutterVersion(), + ); + + createMinimalMockProjectFiles(createWorkspace: false); + + fakeProcessManager.addCommands([ + setUpFakeXcodeBuildHandler('Debug', hasWorkspace: false), + ]); + + await createTestCommandRunner( + command, + ).run(const ['build', 'macos', '--debug', '--no-pub']); + + expect(fakeProcessManager, hasNoRemainingExpectations); + }, + overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => fakeProcessManager, + Pub: ThrowingPub.new, + Platform: () => macosPlatform, + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + OperatingSystemUtils: () => FakeOperatingSystemUtils(hostPlatform: HostPlatform.darwin_x64), + }, + ); + testUsingContext( 'macOS build fails on non-macOS platform', () async { diff --git a/packages/flutter_tools/test/general.shard/migrations/lldb_init_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/lldb_init_migration_test.dart index 868a5bfb987ff..d37946f226f91 100644 --- a/packages/flutter_tools/test/general.shard/migrations/lldb_init_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/migrations/lldb_init_migration_test.dart @@ -74,35 +74,6 @@ void main() { ); }); - testWithoutContext('fails if Xcode workspace not found', () async { - final memoryFileSystem = MemoryFileSystem(); - final testLogger = BufferLogger.test(); - final project = FakeXcodeProject( - platform: SupportedPlatform.ios.name, - fileSystem: memoryFileSystem, - logger: testLogger, - ); - _createProjectFiles(project); - project.xcodeWorkspace = null; - - final migration = LLDBInitMigration( - project, - BuildInfo.debug, - testLogger, - environmentType: EnvironmentType.physical, - fileSystem: memoryFileSystem, - ); - await migration.migrate(); - expect(testLogger.traceText, contains('Xcode workspace not found.')); - expect( - testLogger.errorText, - contains( - 'Running Flutter in debug mode on new iOS versions requires a LLDB Init File, but the ' - 'scheme does not have it set.', - ), - ); - }); - testWithoutContext('fails if scheme not found', () async { final memoryFileSystem = MemoryFileSystem(); final testLogger = BufferLogger.test(); @@ -496,6 +467,36 @@ void main() { ), ); }); + + testWithoutContext('succeeds with no Runner.xcworkspace', () async { + final memoryFileSystem = MemoryFileSystem(); + final testLogger = BufferLogger.test(); + final project = FakeXcodeProject( + platform: SupportedPlatform.ios.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project); + project.xcodeWorkspace = null; + project.xcodeProjectSchemeFile().writeAsStringSync(_validScheme()); + + final migration = LLDBInitMigration( + project, + BuildInfo.debug, + testLogger, + environmentType: EnvironmentType.physical, + fileSystem: memoryFileSystem, + ); + await migration.migrate(); + expect(testLogger.errorText, isEmpty); + expect( + project.xcodeProjectSchemeFile().readAsStringSync(), + _validScheme( + lldbInitFile: + '\n customLLDBInitFile = "\$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"', + ), + ); + }); }); } 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 index 3df6840f32cc1..0cdcc098ea59d 100644 --- 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 @@ -143,35 +143,6 @@ void main() { expect(testLogger.statusText, isEmpty); }); - testWithoutContext('fails if Xcode workspace not found', () async { - final memoryFileSystem = MemoryFileSystem(); - final testLogger = BufferLogger.test(); - final project = FakeXcodeProject( - platform: FlutterDarwinPlatform.ios.name, - fileSystem: memoryFileSystem, - logger: testLogger, - ); - _createProjectFiles(project, FlutterDarwinPlatform.ios, schemeMigrated: false); - project.xcodeWorkspace = null; - - final projectMigration = SwiftPackageManagerIntegrationMigration( - project, - FlutterDarwinPlatform.ios, - BuildInfo.debug, - xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), - logger: testLogger, - fileSystem: memoryFileSystem, - plistParser: FakePlistParser(), - config: FakeConfig(), - ); - 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(); final testLogger = BufferLogger.test(); @@ -558,6 +529,43 @@ void main() { ); }); + testWithoutContext('successfully updates scheme with no Runner.xcworkspace', () async { + final memoryFileSystem = MemoryFileSystem(); + final testLogger = BufferLogger.test(); + final project = FakeXcodeProject( + platform: platform.name, + fileSystem: memoryFileSystem, + logger: testLogger, + ); + _createProjectFiles(project, platform, schemeMigrated: false); + project.xcodeWorkspace = null; + project.xcodeProjectSchemeFile().writeAsStringSync(_validBuildActions(platform)); + + final plistParser = FakePlistParser.multiple([ + _plutilOutput(_allSectionsMigratedAsJson(platform)), + _plutilOutput(_allSectionsMigratedAsJson(platform)), + ]); + project.xcodeProjectInfoFile.writeAsStringSync( + _projectSettings(_allSectionsMigrated(platform)), + ); + final projectMigration = SwiftPackageManagerIntegrationMigration( + project, + platform, + BuildInfo.debug, + xcodeProjectInterpreter: FakeXcodeProjectInterpreter(), + logger: testLogger, + fileSystem: memoryFileSystem, + plistParser: plistParser, + config: FakeConfig(), + ); + + await projectMigration.migrate(); + expect( + project.xcodeProjectSchemeFile().readAsStringSync(), + _validBuildActions(platform, hasFrameworkScript: true), + ); + }); + testWithoutContext('successfully updates scheme with preexisting PreActions', () async { final memoryFileSystem = MemoryFileSystem(); final testLogger = BufferLogger.test(); diff --git a/packages/flutter_tools/test/integration.shard/build_without_xcworkspace_test.dart b/packages/flutter_tools/test/integration.shard/build_without_xcworkspace_test.dart new file mode 100644 index 0000000000000..869d3a6d2ef26 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/build_without_xcworkspace_test.dart @@ -0,0 +1,124 @@ +// 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/error_handling_io.dart'; +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'; + +void main() { + late Directory workingDirectory; + + setUp(() { + workingDirectory = fileSystem.systemTempDirectory.createTempSync( + 'build_without_xcworkspace_test.', + ); + }); + + tearDown(() { + ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true); + }); + + test( + 'flutter build ios succeeds when no .xcworkspace is present', + () async { + await _testBuildWithoutWorkspace( + workingDirectory: workingDirectory, + targetPlatform: 'ios', + buildArgs: ['--debug', '--no-codesign'], + // This file only exists if the app was fully built. + fullyBuiltMarker: (Directory appDirectory) => appDirectory + .childDirectory('build') + .childDirectory('ios') + .childDirectory('iphoneos') + .childDirectory('Runner.app') + .childFile('AppFrameworkInfo.plist'), + ); + }, + skip: !platform.isMacOS, // [intended] Can only build for iOS on macOS. + ); + + test( + 'flutter build macos succeeds when no .xcworkspace is present', + () async { + await _testBuildWithoutWorkspace( + workingDirectory: workingDirectory, + targetPlatform: 'macos', + buildArgs: ['--debug'], + // This file only exists if the app was fully built. + fullyBuiltMarker: (Directory appDirectory) => appDirectory + .childDirectory('build') + .childDirectory('macos') + .childDirectory('Build') + .childDirectory('Products') + .childDirectory('Debug') + .childDirectory('App.framework') + .childDirectory('Resources') + .childFile('Info.plist'), + ); + }, + skip: !platform.isMacOS, // [intended] Can only build for macOS on macOS. + ); +} + +/// Creates an app, removes its `Runner.xcworkspace`, and verifies the build +/// still succeeds. +Future _testBuildWithoutWorkspace({ + required Directory workingDirectory, + required String targetPlatform, + required List buildArgs, + required File Function(Directory appDirectory) fullyBuiltMarker, +}) async { + const appName = 'no_workspace_app'; + + final ProcessResult createResult = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--org', + 'io.flutter.devicelab', + appName, + '--platforms=$targetPlatform', + ], workingDirectory: workingDirectory.path); + expect( + createResult.exitCode, + 0, + reason: + 'Failed to create app: \n' + 'stdout: \n${createResult.stdout}\n' + 'stderr: \n${createResult.stderr}\n', + ); + + final Directory appDirectory = workingDirectory.childDirectory(appName); + + final Directory workspace = appDirectory + .childDirectory(targetPlatform) + .childDirectory('Runner.xcworkspace'); + ErrorHandlingFileSystem.deleteIfExists(workspace, recursive: true); + expect(workspace, isNot(exists)); + + final ProcessResult buildResult = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'build', + targetPlatform, + ...buildArgs, + ], workingDirectory: appDirectory.path); + expect( + buildResult.exitCode, + 0, + reason: + 'Failed to build the app without a .xcworkspace: \n' + 'stdout: \n${buildResult.stdout}\n' + 'stderr: \n${buildResult.stderr}\n', + ); + + // The build must not require or silently recreate the workspace; that is + // the regression this test guards against. + expect(workspace, isNot(exists)); + expect(fullyBuiltMarker(appDirectory), exists); +} From 974dc3ba062df6eb0e47570b1dd8b73a73eba054 Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:10:14 +0800 Subject: [PATCH 027/147] [Impeller]Use the IO context for OpenGL program setup (#185723) Fix https://github.com/flutter/flutter/issues/176657 This PR can improve app startup time for impeller+GLES on low-end devices. https://github.com/flutter/flutter/pull/180022 has provided solution for impeller + vulkan. Impeller+GLES has a special case: resource loading cannot be arbitrarily placed on other threads; it needs to be done on the I/O thread because this thread shares the context with the GPU thread. Therefore, this PR introduces the I/O task runner into PipelineCompileQueueGLES. However, since the I/O task runner may handle other tasks, we cannot put all jobs into the I/O thread at once. My current solution is to wait for the previous job to finish before putting the next one into the I/O thread. Main changes: 1. Introduce fml::RefPtr into PipelineCompileQueueGLES. 2. Extract new class PipelineCompileQueueVulkan for vulkan, because the runners are on different threads. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Jason Simmons --- engine/src/flutter/fml/BUILD.gn | 3 + engine/src/flutter/fml/task_runner_util.cc | 34 +++ engine/src/flutter/fml/task_runner_util.h | 57 +++++ .../flutter/fml/task_runner_util_unittests.cc | 58 +++++ engine/src/flutter/impeller/renderer/BUILD.gn | 1 + .../impeller/renderer/backend/gles/BUILD.gn | 4 + .../renderer/backend/gles/context_gles.cc | 15 +- .../renderer/backend/gles/context_gles.h | 6 +- .../gles/pipeline_compile_queue_gles.cc | 72 +++++++ .../gles/pipeline_compile_queue_gles.h | 63 ++++++ .../backend/gles/pipeline_library_gles.cc | 51 +++-- .../backend/gles/pipeline_library_gles.h | 9 +- .../pipeline_compile_queue_gles_unittests.cc | 199 ++++++++++++++++++ .../impeller/renderer/backend/vulkan/BUILD.gn | 3 + .../vulkan/pipeline_compile_queue_vulkan.cc | 45 ++++ .../vulkan/pipeline_compile_queue_vulkan.h | 56 +++++ .../backend/vulkan/pipeline_library_vk.cc | 2 +- .../backend/vulkan/pipeline_library_vk.h | 4 +- ...pipeline_compile_queue_vulkan_unittests.cc | 181 ++++++++++++++++ .../renderer/pipeline_compile_queue.cc | 50 ++--- .../renderer/pipeline_compile_queue.h | 71 +++++-- .../pipeline_compile_queue_unittests.cc | 96 +++++++++ .../src/flutter/shell/common/platform_view.h | 10 + engine/src/flutter/shell/common/rasterizer.cc | 2 + .../shell/common/rasterizer_unittests.cc | 34 +++ engine/src/flutter/shell/common/shell.cc | 21 ++ engine/src/flutter/shell/common/shell.h | 20 ++ .../common/shell_test_platform_view_gl.cc | 5 +- .../flutter/shell/common/shell_unittests.cc | 5 + .../android_context_dynamic_impeller.cc | 8 +- .../android_context_dynamic_impeller.h | 4 +- .../android/android_context_gl_impeller.cc | 21 +- .../android/android_context_gl_impeller.h | 8 +- .../android_context_gl_impeller_unittests.cc | 8 +- .../platform/android/platform_view_android.cc | 13 +- .../Source/FlutterEnginePlatformViewTest.mm | 3 + .../Source/FlutterPlatformViewsTest.mm | 3 + .../Source/FlutterTextInputPluginTest.mm | 3 + .../Source/accessibility_bridge_test.mm | 3 + .../darwin/ios/platform_view_ios_test.mm | 3 + .../shell/platform/embedder/embedder.cc | 1 + .../platform/embedder/embedder_surface.cc | 2 + .../platform/embedder/embedder_surface.h | 6 + .../embedder/embedder_surface_gl_impeller.cc | 9 +- .../embedder/embedder_surface_gl_impeller.h | 4 + .../embedder/platform_view_embedder.cc | 9 + .../embedder/platform_view_embedder.h | 3 + .../platform_view_embedder_unittests.cc | 4 + .../embedder_test_surface_gl_impeller.cc | 6 +- .../flutter/tests/platform_view_unittest.cc | 5 + 50 files changed, 1206 insertions(+), 97 deletions(-) create mode 100644 engine/src/flutter/fml/task_runner_util.cc create mode 100644 engine/src/flutter/fml/task_runner_util.h create mode 100644 engine/src/flutter/fml/task_runner_util_unittests.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc create mode 100644 engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc diff --git a/engine/src/flutter/fml/BUILD.gn b/engine/src/flutter/fml/BUILD.gn index 69f88e485a7f4..85c6171dbd200 100644 --- a/engine/src/flutter/fml/BUILD.gn +++ b/engine/src/flutter/fml/BUILD.gn @@ -85,6 +85,8 @@ source_set("fml") { "task_queue_id.h", "task_runner.cc", "task_runner.h", + "task_runner_util.cc", + "task_runner_util.h", "task_source.cc", "task_source.h", "thread.cc", @@ -355,6 +357,7 @@ if (enable_unittests) { "synchronization/semaphore_unittest.cc", "synchronization/sync_switch_unittest.cc", "synchronization/waitable_event_unittest.cc", + "task_runner_util_unittests.cc", "task_source_unittests.cc", "thread_unittests.cc", "time/chrono_timestamp_provider.cc", diff --git a/engine/src/flutter/fml/task_runner_util.cc b/engine/src/flutter/fml/task_runner_util.cc new file mode 100644 index 0000000000000..2e12163bf9059 --- /dev/null +++ b/engine/src/flutter/fml/task_runner_util.cc @@ -0,0 +1,34 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/task_runner_util.h" + +namespace fml { + +WrapperBasicTaskRunner::WrapperBasicTaskRunner( + fml::RefPtr task_runner) + : task_runner_(std::move(task_runner)) {} + +void WrapperBasicTaskRunner::PostTask(const fml::closure& task) { + task_runner_->PostTask(task); +} + +ConditionalBasicTaskRunner::ConditionalBasicTaskRunner( + fml::RefPtr task_runner, + std::function is_usable) + : task_runner_(std::move(task_runner)), + is_usable_( + std::make_shared>(std::move(is_usable))) {} + +void ConditionalBasicTaskRunner::PostTask(const fml::closure& task) { + auto task_wrapper = [task, weak_is_usable = std::weak_ptr(is_usable_)] { + std::shared_ptr> is_usable = weak_is_usable.lock(); + if (is_usable && (*is_usable)()) { + task(); + } + }; + task_runner_->PostTask(task_wrapper); +} + +} // namespace fml diff --git a/engine/src/flutter/fml/task_runner_util.h b/engine/src/flutter/fml/task_runner_util.h new file mode 100644 index 0000000000000..e766256f4bfa4 --- /dev/null +++ b/engine/src/flutter/fml/task_runner_util.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_TASK_RUNNER_UTIL_H_ +#define FLUTTER_FML_TASK_RUNNER_UTIL_H_ + +#include + +#include "flutter/fml/memory/ref_ptr.h" +#include "flutter/fml/task_runner.h" + +namespace fml { + +/// A BasicTaskRunner that posts tasks to another task runner. +/// +/// This can be used to adapt an fml::RefPtr to APIs that +/// take a BasicTaskRunner that is not managed by fml::RefPtr. +class WrapperBasicTaskRunner : public BasicTaskRunner { + public: + explicit WrapperBasicTaskRunner(fml::RefPtr task_runner); + + virtual ~WrapperBasicTaskRunner() = default; + + void PostTask(const fml::closure& task) override; + + private: + fml::RefPtr task_runner_; + + FML_DISALLOW_COPY_AND_ASSIGN(WrapperBasicTaskRunner); +}; + +/// A BasicTaskRunner that wraps another task runner and takes a function +/// that indicates whether that task runner is still usable. +/// +/// Before each posted task is run, ConditionalBasicTaskRunner will call the +/// is_usable function on the underlying task runner's thread. If is_usable +/// returns false, then the task will not be executed. +class ConditionalBasicTaskRunner : public BasicTaskRunner { + public: + explicit ConditionalBasicTaskRunner(fml::RefPtr task_runner, + std::function is_usable); + + virtual ~ConditionalBasicTaskRunner() = default; + + void PostTask(const fml::closure& task) override; + + private: + fml::RefPtr task_runner_; + const std::shared_ptr> is_usable_; + + FML_DISALLOW_COPY_AND_ASSIGN(ConditionalBasicTaskRunner); +}; + +} // namespace fml + +#endif // FLUTTER_FML_TASK_RUNNER_UTIL_H_ diff --git a/engine/src/flutter/fml/task_runner_util_unittests.cc b/engine/src/flutter/fml/task_runner_util_unittests.cc new file mode 100644 index 0000000000000..0579ff9b980f2 --- /dev/null +++ b/engine/src/flutter/fml/task_runner_util_unittests.cc @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/fml/task_runner_util.h" +#include "flutter/fml/thread.h" +#include "gtest/gtest.h" + +namespace fml { +namespace testing { + +TEST(TaskRunnerUtilTests, WrapperBasicTaskRunnerPostTask) { + fml::Thread thread; + + WrapperBasicTaskRunner wrapper(thread.GetTaskRunner()); + + std::thread::id wrapper_thread_id; + wrapper.PostTask([&]() { wrapper_thread_id = std::this_thread::get_id(); }); + + thread.Join(); + + EXPECT_NE(wrapper_thread_id, std::this_thread::get_id()); +} + +TEST(TaskRunnerUtilTests, ConditionalBasicTaskRunnerPostTask) { + fml::Thread thread; + std::atomic_bool active = true; + ConditionalBasicTaskRunner runner(thread.GetTaskRunner(), + [&active]() -> bool { return active; }); + + fml::AutoResetWaitableEvent latch; + std::atomic_bool task1_called = false; + runner.PostTask([&]() { + task1_called.store(true); + latch.Signal(); + }); + latch.Wait(); + + active.store(false); + + std::atomic_bool task2_called = false; + runner.PostTask([&]() { task2_called.store(true); }); + + thread.GetTaskRunner()->PostTask([&]() { latch.Signal(); }); + latch.Wait(); + + thread.Join(); + + EXPECT_TRUE(task1_called.load()); + EXPECT_FALSE(task2_called.load()); +} + +} // namespace testing +} // namespace fml diff --git a/engine/src/flutter/impeller/renderer/BUILD.gn b/engine/src/flutter/impeller/renderer/BUILD.gn index ee339a915fe95..fa27c57d1e9e5 100644 --- a/engine/src/flutter/impeller/renderer/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/BUILD.gn @@ -108,6 +108,7 @@ template("renderer_unittests_component") { "blit_pass_unittests.cc", "capabilities_unittests.cc", "device_buffer_unittests.cc", + "pipeline_compile_queue_unittests.cc", "pipeline_descriptor_unittests.cc", "pipeline_library_unittests.cc", "pool_unittests.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn index deec3f1c2fd69..6ae9f50baec3d 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn @@ -25,6 +25,7 @@ impeller_component("gles_unittests") { "test/mock_gles.cc", "test/mock_gles.h", "test/mock_gles_unittests.cc", + "test/pipeline_compile_queue_gles_unittests.cc", "test/pipeline_library_gles_unittests.cc", "test/proc_table_gles_unittests.cc", "test/reactor_unittests.cc", @@ -35,6 +36,7 @@ impeller_component("gles_unittests") { ] deps = [ ":gles", + "//flutter/fml", "//flutter/impeller/playground:playground_test", "//flutter/testing:testing_lib", ] @@ -69,6 +71,8 @@ impeller_component("gles") { "gpu_tracer_gles.h", "handle_gles.cc", "handle_gles.h", + "pipeline_compile_queue_gles.cc", + "pipeline_compile_queue_gles.h", "pipeline_gles.cc", "pipeline_gles.h", "pipeline_library_gles.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc index 3cdadd3bd29e8..ff274c48a45e7 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc @@ -22,16 +22,19 @@ std::shared_ptr ContextGLES::Create( const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing) { - return std::shared_ptr(new ContextGLES( - flags, std::move(gl), shader_libraries, enable_gpu_tracing)); + bool enable_gpu_tracing, + std::shared_ptr io_task_runner) { + return std::shared_ptr( + new ContextGLES(flags, std::move(gl), shader_libraries, + enable_gpu_tracing, std::move(io_task_runner))); } ContextGLES::ContextGLES( const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries_mappings, - bool enable_gpu_tracing) + bool enable_gpu_tracing, + std::shared_ptr io_task_runner) : Context(flags) { reactor_ = std::make_shared(std::move(gl)); if (!reactor_->IsValid()) { @@ -52,8 +55,8 @@ ContextGLES::ContextGLES( // Create the pipeline library. { - pipeline_library_ = - std::shared_ptr(new PipelineLibraryGLES(reactor_)); + pipeline_library_ = std::shared_ptr( + new PipelineLibraryGLES(reactor_, std::move(io_task_runner))); } // Create allocators. diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h index 1c54844b5e256..ba4a2847213f0 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h @@ -28,7 +28,8 @@ class ContextGLES final : public Context, const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing); + bool enable_gpu_tracing, + std::shared_ptr io_task_runner = nullptr); // |Context| ~ContextGLES() override; @@ -70,7 +71,8 @@ class ContextGLES final : public Context, const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing); + bool enable_gpu_tracing, + std::shared_ptr io_task_runner = nullptr); // |Context| std::string DescribeGpuModel() const override; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc new file mode 100644 index 0000000000000..b8955445deffe --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" + +namespace impeller { + +std::shared_ptr PipelineCompileQueueGLES::Create( + std::shared_ptr worker_task_runner) { + if (!worker_task_runner) { + return nullptr; + } + return std::shared_ptr( + new PipelineCompileQueueGLES(std::move(worker_task_runner))); +} + +PipelineCompileQueueGLES::PipelineCompileQueueGLES( + std::shared_ptr worker_task_runner) + : worker_task_runner_(std::move(worker_task_runner)) {} + +PipelineCompileQueueGLES::~PipelineCompileQueueGLES() = default; + +void PipelineCompileQueueGLES::OnJobAdded() { + // To prevent potential deadlocks and reduce lock contention, avoid calling + // external or virtual methods (such as DrainPendingJobs, which posts tasks + // to the task runner) while holding a mutex. Instead, minimize the scope of + // the lock by using a local boolean flag to trigger the draining process + // outside the lock block. + bool should_drain = false; + { + Lock lock(processing_mutex_); + if (!is_processing_) { + is_processing_ = true; + should_drain = true; + } + } + if (should_drain) { + DrainPendingJobs(); + } +} + +void PipelineCompileQueueGLES::PostJob(const fml::closure& job) { + if (!job) { + return; + } + + worker_task_runner_->PostTask(job); +} + +void PipelineCompileQueueGLES::DrainPendingJobs() { + PostJob([weak_queue = weak_from_this()]() { + if (auto queue = std::static_pointer_cast( + weak_queue.lock())) { + queue->DoOneJob(); + { + Lock lock(queue->processing_mutex_); + if (!queue->HasPendingJobs()) { + queue->is_processing_ = false; + return; + } + } + queue->DrainPendingJobs(); + } + }); +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h new file mode 100644 index 0000000000000..cf013d6950e9d --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ +#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ + +#include "flutter/fml/closure.h" +#include "flutter/fml/task_runner.h" +#include "impeller/base/thread.h" +#include "impeller/renderer/pipeline_compile_queue.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A task queue designed for managing compilation of pipeline state +/// objects for OpenGL ES backend. +/// +/// This subclass uses a fml::TaskRunner as the worker task runner +/// and implements a sequential job processing mechanism to prevent +/// blocking the IO task runner. +/// +/// Key characteristics: +/// - Uses fml::RefPtr for worker_task_runner_ +/// - Processes jobs sequentially: loads one job at a time before +/// proceeding to the next, preventing IO task runner blocking +/// - Uses DrainPendingJobs() to recursively process jobs one by one +/// - Employs is_processing_ flag and processing_mutex_ to control +/// sequential processing +/// +/// The sequential processing ensures that pipeline compilation jobs +/// do not overwhelm the task runner, which is particularly +/// important for GLES backend where resource loading patterns +/// differ from Vulkan. +/// +class PipelineCompileQueueGLES : public PipelineCompileQueue { + public: + static std::shared_ptr Create( + std::shared_ptr worker_task_runner); + + ~PipelineCompileQueueGLES() override; + + PipelineCompileQueueGLES(const PipelineCompileQueueGLES&) = delete; + + PipelineCompileQueueGLES& operator=(const PipelineCompileQueueGLES&) = delete; + + void PostJob(const fml::closure& job) override; + + void OnJobAdded() override; + + private: + explicit PipelineCompileQueueGLES( + std::shared_ptr worker_task_runner); + void DrainPendingJobs(); + + std::shared_ptr worker_task_runner_; + Mutex processing_mutex_; + bool is_processing_ IPLR_GUARDED_BY(processing_mutex_) = false; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc index 240891dfc4b60..f5643ee559018 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc @@ -16,8 +16,12 @@ namespace impeller { -PipelineLibraryGLES::PipelineLibraryGLES(std::shared_ptr reactor) - : reactor_(std::move(reactor)) {} +PipelineLibraryGLES::PipelineLibraryGLES( + std::shared_ptr reactor, + std::shared_ptr io_task_runner) + : reactor_(std::move(reactor)), + compile_queue_( + PipelineCompileQueueGLES::Create(std::move(io_task_runner))) {} static std::string GetShaderInfoLog(const ProcTableGLES& gl, GLuint shader) { GLint log_length = 0; @@ -296,17 +300,34 @@ PipelineFuture PipelineLibraryGLES::GetPipeline( PipelineFuture{descriptor, promise->get_future()}; pipelines_[descriptor] = pipeline_future; - const auto result = reactor_->AddOperation([promise, // - weak_this = weak_from_this(), // - descriptor, // - vert_function, // - frag_function, // - threadsafe // - ](const ReactorGLES& reactor) { - promise->set_value(CreatePipeline(weak_this, descriptor, vert_function, - frag_function, threadsafe)); - }); - FML_CHECK(result); + std::weak_ptr weak_this = weak_from_this(); + std::shared_ptr reactor = reactor_; + auto generation_task = [promise, weak_this, descriptor, vert_function, + frag_function, threadsafe, reactor]() { + auto thiz = weak_this.lock(); + if (!thiz) { + promise->set_value(nullptr); + return; + } + const bool result = reactor->AddOperation([promise, // + weak_this, // + descriptor, // + vert_function, // + frag_function, // + threadsafe // + ](const ReactorGLES& reactor) { + promise->set_value(CreatePipeline(weak_this, descriptor, vert_function, + frag_function, threadsafe)); + }); + FML_CHECK(result); + }; + + if (async && compile_queue_) { + compile_queue_->PostJobForDescriptor(descriptor, + std::move(generation_task)); + } else { + generation_task(); + } return pipeline_future; } @@ -373,4 +394,8 @@ void PipelineLibraryGLES::SetProgramForKey( programs_[key] = std::move(program); } +PipelineCompileQueue* PipelineLibraryGLES::GetPipelineCompileQueue() const { + return compile_queue_.get(); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h index 25c51c8113d38..f2ce764f3b7eb 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h @@ -9,7 +9,9 @@ #include #include "flutter/fml/hash_combine.h" +#include "flutter/fml/task_runner.h" #include "impeller/base/thread.h" +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" #include "impeller/renderer/backend/gles/reactor_gles.h" #include "impeller/renderer/backend/gles/unique_handle_gles.h" #include "impeller/renderer/pipeline_library.h" @@ -91,8 +93,11 @@ class PipelineLibraryGLES final PipelineMap pipelines_; Mutex programs_mutex_; ProgramMap programs_ IPLR_GUARDED_BY(programs_mutex_); + std::shared_ptr compile_queue_; - explicit PipelineLibraryGLES(std::shared_ptr reactor); + explicit PipelineLibraryGLES( + std::shared_ptr reactor, + std::shared_ptr io_task_runner); // |PipelineLibrary| bool IsValid() const override; @@ -127,6 +132,8 @@ class PipelineLibraryGLES final void SetProgramForKey(const ProgramKey& key, std::shared_ptr program); + // |PipelineLibrary| + PipelineCompileQueue* GetPipelineCompileQueue() const override; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc new file mode 100644 index 0000000000000..ff726faa11f20 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc @@ -0,0 +1,199 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" + +#include +#include +#include + +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/task_runner.h" +#include "flutter/fml/task_runner_util.h" +#include "flutter/fml/thread.h" +#include "flutter/testing/testing.h" +#include "impeller/renderer/pipeline_descriptor.h" + +namespace impeller { +namespace testing { + +namespace { + +std::shared_ptr CreateBasicTaskRunner( + const fml::Thread& thread) { + return std::make_shared(thread.GetTaskRunner()); +} + +} // namespace + +TEST(PipelineCompileQueueGLESTest, CreateReturnsNullWithNullTaskRunner) { + auto queue = PipelineCompileQueueGLES::Create(nullptr); + EXPECT_EQ(queue, nullptr); +} + +TEST(PipelineCompileQueueGLESTest, CreateSucceedsWithValidTaskRunner) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + EXPECT_NE(queue, nullptr); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, PostJobDoesNothingWithNullClosure) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + ASSERT_NE(queue, nullptr); + queue->PostJob(nullptr); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, OnJobAddedProcessesJobsSequentially) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + ASSERT_NE(queue, nullptr); + + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(3); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(completed_jobs, 3); + + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, + PostJobForDescriptorWithDuplicateRunsEagerly) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + ASSERT_NE(queue, nullptr); + + std::atomic first_job_count{0}; + std::atomic second_job_count{0}; + fml::CountDownLatch latch(2); + + PipelineDescriptor desc; + + queue->PostJobForDescriptor(desc, [&]() { + first_job_count++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc, [&]() { + second_job_count++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(first_job_count, 1); + EXPECT_EQ(second_job_count, 1); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, IsProcessingResetsAfterAllJobsComplete) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + ASSERT_NE(queue, nullptr); + + fml::CountDownLatch latch(1); + + queue->PostJobForDescriptor(PipelineDescriptor{}, + [&]() { latch.CountDown(); }); + + latch.Wait(); + + fml::CountDownLatch latch2(1); + queue->PostJobForDescriptor(PipelineDescriptor{}, + [&]() { latch2.CountDown(); }); + + latch2.Wait(); + + SUCCEED(); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, DestroyQueueWithPendingTasks) { + fml::Thread thread; + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(3); + + { + auto queue = + PipelineCompileQueueGLES::Create(CreateBasicTaskRunner(thread)); + ASSERT_NE(queue, nullptr); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + // Queue will be destroyed here with pending jobs. + // The destructor should ensure that the pending jobs are either executed + // or posted to the queue's thread. + } + + // Wait for completion of the jobs. + latch.Wait(); + EXPECT_EQ(completed_jobs, 3); + + thread.Join(); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn index 3135074b219ec..afa6489d4bf72 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn @@ -41,6 +41,7 @@ impeller_component("vulkan_unittests") { "surface_context_vk_unittests.cc", "test/gpu_tracer_unittests.cc", "test/mock_vulkan_unittests.cc", + "test/pipeline_compile_queue_vulkan_unittests.cc", "test/sampler_library_vk_unittests.cc", "test/swapchain_unittests.cc", ] @@ -94,6 +95,8 @@ impeller_component("vulkan") { "pipeline_cache_data_vk.h", "pipeline_cache_vk.cc", "pipeline_cache_vk.h", + "pipeline_compile_queue_vulkan.cc", + "pipeline_compile_queue_vulkan.h", "pipeline_library_vk.cc", "pipeline_library_vk.h", "pipeline_vk.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc new file mode 100644 index 0000000000000..aed83a4cebdeb --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" + +namespace impeller { + +std::shared_ptr PipelineCompileQueueVulkan::Create( + std::shared_ptr worker_task_runner) { + if (!worker_task_runner) { + return nullptr; + } + return std::shared_ptr( + new PipelineCompileQueueVulkan(std::move(worker_task_runner))); +} + +PipelineCompileQueueVulkan::PipelineCompileQueueVulkan( + std::shared_ptr worker_task_runner) + : PipelineCompileQueue(), + worker_task_runner_(std::move(worker_task_runner)) {} + +PipelineCompileQueueVulkan::~PipelineCompileQueueVulkan() = default; + +void PipelineCompileQueueVulkan::OnJobAdded() { + PostJob([weak_queue = weak_from_this()]() { + if (auto queue = std::static_pointer_cast( + weak_queue.lock())) { + queue->DoOneJob(); + } + }); +} + +void PipelineCompileQueueVulkan::PostJob(const fml::closure& job) { + if (!job) { + return; + } + + worker_task_runner_->PostTask(job); +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h new file mode 100644 index 0000000000000..5a65c0ebc4997 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ +#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ + +#include "flutter/fml/closure.h" +#include "flutter/fml/task_runner.h" +#include "impeller/renderer/pipeline_compile_queue.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A task queue designed for managing compilation of pipeline state +/// objects for Vulkan backend. +/// +/// This subclass uses a fml::BasicTaskRunner as the worker task +/// runner and dispatches compile jobs directly without sequential +/// processing constraints. +/// +/// Key characteristics: +/// - Uses std::shared_ptr for +/// worker_task_runner_ +/// - Dispatches jobs directly to the task runner in OnJobAdded() +/// - Does not implement sequential processing like GLES version +/// +/// The Vulkan backend benefits from the parallel nature of pipeline +/// compilation, allowing multiple compile jobs to be processed +/// concurrently through the task runner. +/// +class PipelineCompileQueueVulkan : public PipelineCompileQueue { + public: + static std::shared_ptr Create( + std::shared_ptr worker_task_runner); + + ~PipelineCompileQueueVulkan() override; + + PipelineCompileQueueVulkan(const PipelineCompileQueueVulkan&) = delete; + + PipelineCompileQueueVulkan& operator=(const PipelineCompileQueueVulkan&) = + delete; + + void PostJob(const fml::closure& job) override; + + void OnJobAdded() override; + + private: + explicit PipelineCompileQueueVulkan( + std::shared_ptr worker_task_runner); + std::shared_ptr worker_task_runner_; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index 95b3351049b39..6e8d9544f7dc5 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc @@ -27,7 +27,7 @@ PipelineLibraryVK::PipelineLibraryVK( device_holder, std::move(cache_directory))), worker_task_runner_(std::move(worker_task_runner)), - compile_queue_(PipelineCompileQueue::Create(worker_task_runner_)) { + compile_queue_(PipelineCompileQueueVulkan::Create(worker_task_runner_)) { FML_DCHECK(worker_task_runner_); if (!pso_cache_->IsValid() || !worker_task_runner_) { return; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h index 3ead3c7dc700d..4304db9b58293 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h @@ -13,10 +13,10 @@ #include "impeller/base/thread.h" #include "impeller/renderer/backend/vulkan/compute_pipeline_vk.h" #include "impeller/renderer/backend/vulkan/pipeline_cache_vk.h" +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" #include "impeller/renderer/backend/vulkan/pipeline_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/pipeline.h" -#include "impeller/renderer/pipeline_compile_queue.h" #include "impeller/renderer/pipeline_library.h" namespace impeller { @@ -49,7 +49,7 @@ class PipelineLibraryVK final PipelineKey pipeline_key_ IPLR_GUARDED_BY(pipelines_mutex_) = 1; bool is_valid_ = false; bool cache_dirty_ = false; - std::shared_ptr compile_queue_; + std::shared_ptr compile_queue_; PipelineLibraryVK( const std::shared_ptr& device_holder, diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc new file mode 100644 index 0000000000000..6ee373535fd39 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc @@ -0,0 +1,181 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" + +#include +#include +#include +#include + +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/task_runner.h" +#include "flutter/testing/testing.h" +#include "impeller/renderer/pipeline_descriptor.h" + +namespace impeller { +namespace testing { + +TEST(PipelineCompileQueueVulkanTest, CreateSucceedsWithValidTaskRunner) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + EXPECT_NE(queue, nullptr); +} + +TEST(PipelineCompileQueueVulkanTest, PostJobDoesNothingWithNullClosure) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + queue->PostJob(nullptr); +} + +TEST(PipelineCompileQueueVulkanTest, OnJobAddedProcessesJobsInParallel) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic concurrent_jobs{0}; + std::atomic max_concurrent{0}; + fml::CountDownLatch latch(3); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_GE(max_concurrent.load(), 1); +} + +TEST(PipelineCompileQueueVulkanTest, + PostJobForDescriptorWithDuplicateRunsEagerly) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic first_job_count{0}; + std::atomic second_job_count{0}; + fml::CountDownLatch latch(2); + + PipelineDescriptor desc; + + queue->PostJobForDescriptor(desc, [&]() { + first_job_count++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc, [&]() { + second_job_count++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(first_job_count, 1); + EXPECT_EQ(second_job_count, 1); +} + +TEST(PipelineCompileQueueVulkanTest, MultipleJobsCompleteSuccessfully) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(5); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + PipelineDescriptor desc4; + desc4.SetSampleCount(SampleCount::kCount4); + desc4.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc5; + desc5.SetSampleCount(SampleCount::kCount4); + desc5.SetCullMode(CullMode::kFrontFace); + + // Post 5 jobs with distinct descriptors + queue->PostJobForDescriptor(desc1, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc4, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc5, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(completed_jobs, 5); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc index 124e11e177346..da10b9be100b8 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc @@ -9,16 +9,6 @@ namespace impeller { -std::shared_ptr PipelineCompileQueue::Create( - std::shared_ptr worker_task_runner) { - return std::shared_ptr( - new PipelineCompileQueue(std::move(worker_task_runner))); -} - -PipelineCompileQueue::PipelineCompileQueue( - std::shared_ptr worker_task_runner) - : worker_task_runner_(std::move(worker_task_runner)) {} - PipelineCompileQueue::~PipelineCompileQueue() { FinishAllJobs(); } @@ -29,31 +19,33 @@ bool PipelineCompileQueue::PostJobForDescriptor(const PipelineDescriptor& desc, return false; } - { - Lock lock(pending_jobs_mutex_); - auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job)); - if (!insertion_result.second) { - // This bit is being extremely conservative. If insertion did not take - // place, someone gave the compile queue a job for the same description. - // This is highly unusual but technically not impossible. Just run the job - // eagerly. - FML_LOG(ERROR) << "Got multiple compile jobs for the same descriptor. " + if (!AddJob(desc, job)) { + // This bit is being extremely conservative. If insertion did not take + // place, someone gave the compile queue a job for the same description. + // This is highly unusual but technically not impossible. Just run the job + // eagerly. + FML_LOG(WARNING) << "Got multiple compile jobs for the same descriptor. " "Running eagerly."; - // Don't invoke the job here has there are we have currently acquired a - // mutex. - worker_task_runner_->PostTask(job); - return true; - } + PostJob(job); + return true; } - worker_task_runner_->PostTask([weak_queue = weak_from_this()]() { - if (auto queue = weak_queue.lock()) { - queue->DoOneJob(); - } - }); + OnJobAdded(); return true; } +bool PipelineCompileQueue::AddJob(const PipelineDescriptor& desc, + const fml::closure& job) { + Lock lock(pending_jobs_mutex_); + auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job)); + return insertion_result.second; +} + +bool PipelineCompileQueue::HasPendingJobs() { + Lock lock(pending_jobs_mutex_); + return !pending_jobs_.empty(); +} + fml::closure PipelineCompileQueue::TakeNextJob() { Lock lock(pending_jobs_mutex_); if (pending_jobs_.empty()) { diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h index d166679b3798f..901ac76977431 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h @@ -39,13 +39,12 @@ namespace impeller { /// entirely optional. The queue skipping mechanism all assume the /// optional availability of a compile queue. /// -class PipelineCompileQueue final +class PipelineCompileQueue : public std::enable_shared_from_this { public: - static std::shared_ptr Create( - std::shared_ptr worker_task_runner); + PipelineCompileQueue() = default; - ~PipelineCompileQueue(); + virtual ~PipelineCompileQueue(); PipelineCompileQueue(const PipelineCompileQueue&) = delete; @@ -72,26 +71,68 @@ class PipelineCompileQueue final /// void PerformJobEagerly(const PipelineDescriptor& desc); + protected: + //---------------------------------------------------------------------------- + /// @brief Post a compilation job to the worker task runner. + /// + /// This is a pure virtual function that must be implemented by + /// subclasses. It is responsible for actually dispatching the + /// job closure to the appropriate task runner for execution. + /// + /// @param[in] job The compilation job closure to post + /// + virtual void PostJob(const fml::closure& job) = 0; + + //---------------------------------------------------------------------------- + /// @brief Called by PostJobForDescriptor after a job has been + /// successfully added to the queue. Subclasses must implement + /// this to define their scheduling strategy. + /// + /// The default implementation for duplicate descriptors is to + /// run the job eagerly. Subclasses can override this behavior + /// by checking for duplicates before calling the base class. + /// + virtual void OnJobAdded() = 0; + + //---------------------------------------------------------------------------- + /// @brief Execute one pending compilation job from the queue. + /// + /// This method retrieves and executes a single job from the + /// pending jobs queue. It is typically called by subclasses + /// when they are ready to process the next job in the queue. + /// + void DoOneJob(); + + //---------------------------------------------------------------------------- + /// @brief Add a compilation job to the pending queue for the specified + /// descriptor. + /// + /// @param[in] desc The pipeline descriptor that identifies the job + /// @param[in] job The compilation job closure to add + /// + /// @return True if the job was successfully added to the queue, false + /// if a job for this descriptor already exists. + /// + bool AddJob(const PipelineDescriptor& desc, const fml::closure& job); + + //---------------------------------------------------------------------------- + /// @brief Check if there are any pending compilation jobs in the queue. + /// + /// @return True if there are pending jobs waiting to be processed, + /// false otherwise. + /// + bool HasPendingJobs(); + private: - std::shared_ptr worker_task_runner_; Mutex pending_jobs_mutex_; - size_t priorities_elevated_ = {}; - std::unordered_map, ComparableEqual> pending_jobs_ IPLR_GUARDED_BY(pending_jobs_mutex_); - - explicit PipelineCompileQueue( - std::shared_ptr worker_task_runner); - + size_t priorities_elevated_ = {}; fml::closure TakeJob(const PipelineDescriptor& desc); - fml::closure TakeNextJob(); - - void DoOneJob(); - void FinishAllJobs(); }; diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc new file mode 100644 index 0000000000000..14be51dcd1556 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/pipeline_compile_queue.h" + +#include + +#include "flutter/fml/closure.h" +#include "flutter/testing/testing.h" + +namespace impeller { +namespace testing { + +class TestPipelineCompileQueue : public PipelineCompileQueue { + public: + void PostJob(const fml::closure& job) override { + if (job) { + job(); + } + } + + void OnJobAdded() override {} + + bool AddJobForTest(const PipelineDescriptor& desc, const fml::closure& job) { + return AddJob(desc, job); + } + + bool HasPendingJobsForTest() { return HasPendingJobs(); } +}; + +TEST(PipelineCompileQueueTest, AddJobReturnsTrueForNewDescriptor) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + bool result = queue.AddJobForTest(desc, job); + EXPECT_TRUE(result); +} + +TEST(PipelineCompileQueueTest, AddJobReturnsFalseForDuplicateDescriptor) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job1_executed = false; + bool job2_executed = false; + fml::closure job1 = [&job1_executed]() { job1_executed = true; }; + fml::closure job2 = [&job2_executed]() { job2_executed = true; }; + + bool result1 = queue.AddJobForTest(desc, job1); + bool result2 = queue.AddJobForTest(desc, job2); + + EXPECT_TRUE(result1); + EXPECT_FALSE(result2); +} + +TEST(PipelineCompileQueueTest, HasPendingJobsReturnsCorrectState) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + fml::closure job = []() {}; + + EXPECT_FALSE(queue.HasPendingJobsForTest()); + + queue.AddJobForTest(desc, job); + EXPECT_TRUE(queue.HasPendingJobsForTest()); +} + +TEST(PipelineCompileQueueTest, PerformJobEagerlyExecutesJob) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + queue.AddJobForTest(desc, job); + queue.PerformJobEagerly(desc); + + EXPECT_TRUE(job_executed); + EXPECT_FALSE(queue.HasPendingJobsForTest()); +} + +TEST(PipelineCompileQueueTest, FinishAllJobsDrainsQueue) { + auto queue = std::make_shared(); + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + queue->AddJobForTest(desc, job); + EXPECT_TRUE(queue->HasPendingJobsForTest()); + + queue.reset(); + + EXPECT_TRUE(job_executed); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/shell/common/platform_view.h b/engine/src/flutter/shell/common/platform_view.h index 95384c3f79366..1c744176364fd 100644 --- a/engine/src/flutter/shell/common/platform_view.h +++ b/engine/src/flutter/shell/common/platform_view.h @@ -386,6 +386,16 @@ class PlatformView { /// @return The settings. /// virtual const Settings& OnPlatformViewGetSettings() const = 0; + + //-------------------------------------------------------------------------- + /// @brief Returns a task runner that executes tasks on the IO thread + /// and stops running tasks after the shell shuts down the IO + /// thread. + /// + /// @return The task runner. + /// + virtual std::shared_ptr + OnPlatformViewGetShutdownSafeIOTaskRunner() const = 0; }; //---------------------------------------------------------------------------- diff --git a/engine/src/flutter/shell/common/rasterizer.cc b/engine/src/flutter/shell/common/rasterizer.cc index 6af8bfc91d5a7..b003f827df24b 100644 --- a/engine/src/flutter/shell/common/rasterizer.cc +++ b/engine/src/flutter/shell/common/rasterizer.cc @@ -129,6 +129,8 @@ void Rasterizer::Teardown() { } #endif // !SLIMPELLER } + context_switch.reset(); + surface_->ClearRenderContext(); surface_.reset(); } diff --git a/engine/src/flutter/shell/common/rasterizer_unittests.cc b/engine/src/flutter/shell/common/rasterizer_unittests.cc index bab565efe08d1..ca1b776f91252 100644 --- a/engine/src/flutter/shell/common/rasterizer_unittests.cc +++ b/engine/src/flutter/shell/common/rasterizer_unittests.cc @@ -1215,6 +1215,40 @@ TEST(RasterizerTest, TeardownFreesResourceCache) { EXPECT_EQ(context->getResourceCachePurgeableBytes(), 0ul); } +TEST(RasterizerTest, TeardownClearsRenderContext) { + std::string test_name = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + ThreadHost thread_host("io.flutter.test." + test_name + ".", + ThreadHost::Type::kPlatform | + ThreadHost::Type::kRaster | ThreadHost::Type::kIo | + ThreadHost::Type::kUi); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + NiceMock delegate; + Settings settings; + ON_CALL(delegate, GetSettings()).WillByDefault(ReturnRef(settings)); + ON_CALL(delegate, GetTaskRunners()).WillByDefault(ReturnRef(task_runners)); + auto rasterizer = std::make_unique(delegate); + auto surface = std::make_unique>(); + bool render_context_is_current = false; + EXPECT_CALL(*surface, MakeRenderContextCurrent()).WillRepeatedly([&]() { + render_context_is_current = true; + return std::make_unique(true); + }); + EXPECT_CALL(*surface, ClearRenderContext()).WillRepeatedly([&]() { + render_context_is_current = false; + return true; + }); + + rasterizer->Setup(std::move(surface)); + EXPECT_TRUE(render_context_is_current); + + rasterizer->Teardown(); + EXPECT_FALSE(render_context_is_current); +} + TEST(RasterizerTest, TeardownNoSurface) { std::string test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 526d2a7984125..2200280ecc4da 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -25,6 +25,7 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/paths.h" +#include "flutter/fml/task_runner_util.h" #include "flutter/fml/trace_event.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/common/base64.h" @@ -545,6 +546,15 @@ Shell::Shell(DartVMRef vm, resource_cache_limit_calculator->AddResourceCacheLimitItem( weak_factory_.GetWeakPtr()); + std::shared_future> weak_io_manager_future( + weak_io_manager_promise_.get_future()); + shutdown_safe_io_task_runner_ = + std::make_shared( + task_runners_.GetIOTaskRunner(), + [weak_io_manager_future = std::move(weak_io_manager_future)] { + return static_cast(weak_io_manager_future.get()); + }); + // Generate a WeakPtrFactory for use with the raster thread. This does not // need to wait on a latch because it can only ever be used from the raster // thread from this class, so we have ordering guarantees. @@ -873,6 +883,7 @@ bool Shell::Setup(std::unique_ptr platform_view, engine_ = std::move(engine); rasterizer_ = std::move(rasterizer); io_manager_ = io_manager; + weak_io_manager_promise_.set_value(io_manager_->GetWeakPtr()); // Set the external view embedder for the rasterizer. auto view_embedder = platform_view_->CreateExternalViewEmbedder(); @@ -951,6 +962,10 @@ fml::WeakPtr Shell::GetIOManager() { return io_manager_->GetWeakPtr(); } +std::shared_ptr Shell::GetShutdownSafeIOTaskRunner() { + return shutdown_safe_io_task_runner_; +} + DartVM* Shell::GetDartVM() { return &vm_; } @@ -1367,6 +1382,12 @@ const Settings& Shell::OnPlatformViewGetSettings() const { return settings_; } +// |PlatformView::Delegate| +std::shared_ptr +Shell::OnPlatformViewGetShutdownSafeIOTaskRunner() const { + return shutdown_safe_io_task_runner_; +} + // |Animator::Delegate| void Shell::OnAnimatorBeginFrame(fml::TimePoint frame_target_time, uint64_t frame_number) { diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 9953a560f5d69..0eb7248946e99 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -280,6 +280,19 @@ class Shell final : public PlatformView::Delegate, /// fml::WeakPtr GetIOManager(); + //---------------------------------------------------------------------------- + /// @brief The IO thread can be used for background tasks, including + /// tasks that perform graphics operations using the resource + /// context. But the IO thread will lose the resource context + /// during shutdown of the Shell. Tasks that require the IO + /// manager or the resource context must not run after that + /// phase of shutdown. + /// + /// @return A BasicTaskRunner that posts tasks to the IO thread but stops + /// running tasks after the Shell shuts down the IO manager. + /// + std::shared_ptr GetShutdownSafeIOTaskRunner(); + // Embedders should call this under low memory conditions to free up // internal caches used. // @@ -480,6 +493,9 @@ class Shell final : public PlatformView::Delegate, fml::WeakPtr weak_platform_view_; // to be shared across threads + std::promise> weak_io_manager_promise_; + std::shared_ptr shutdown_safe_io_task_runner_; + std::unordered_map, ServiceProtocolHandler> // task-runner/function @@ -640,6 +656,10 @@ class Shell final : public PlatformView::Delegate, // |PlatformView::Delegate| const Settings& OnPlatformViewGetSettings() const override; + // |PlatformView::Delegate| + std::shared_ptr + OnPlatformViewGetShutdownSafeIOTaskRunner() const override; + // |PlatformView::Delegate| void LoadDartDeferredLibrary( intptr_t loading_unit_id, diff --git a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc index 5ee5e7b18a935..20383e34e6b5c 100644 --- a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc +++ b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc @@ -8,6 +8,7 @@ #include +#include "flutter/fml/task_runner_util.h" #include "flutter/shell/gpu/gpu_surface_gl_skia.h" #include "impeller/entity/gles/entity_shaders_gles.h" @@ -61,7 +62,9 @@ ShellTestPlatformViewGL::ShellTestPlatformViewGL( return; } impeller_context_ = impeller::ContextGLES::Create( - impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true); + impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true, + std::make_shared( + task_runners.GetIOTaskRunner())); } } diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 60ff1ecbeb213..56ca523fd0f50 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -186,6 +186,11 @@ class MockPlatformViewDelegate : public PlatformView::Delegate { (), (const, override)); + MOCK_METHOD(std::shared_ptr, + OnPlatformViewGetShutdownSafeIOTaskRunner, + (), + (const, override)); + MOCK_METHOD(void, LoadDartDeferredLibrary, (intptr_t loading_unit_id, diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc index c65f926ea86a5..94f38b04f04bb 100644 --- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc +++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc @@ -140,9 +140,11 @@ GetActualRenderingAPIForImpeller( } // namespace AndroidContextDynamicImpeller::AndroidContextDynamicImpeller( - const AndroidContext::ContextSettings& settings) + const AndroidContext::ContextSettings& settings, + std::shared_ptr io_task_runner) : AndroidContext(AndroidRenderingAPI::kImpellerVulkan), - settings_(settings) {} + settings_(settings), + io_task_runner_(std::move(io_task_runner)) {} AndroidContextDynamicImpeller::~AndroidContextDynamicImpeller() = default; @@ -186,7 +188,7 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { if (!vk_context_) { gl_context_ = std::make_shared( std::make_unique(), - settings_.enable_gpu_tracing); + settings_.enable_gpu_tracing, io_task_runner_); } } diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h index 4de037bfa77ba..17aa12fa074af 100644 --- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h +++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h @@ -23,7 +23,8 @@ namespace flutter { class AndroidContextDynamicImpeller : public AndroidContext { public: explicit AndroidContextDynamicImpeller( - const AndroidContext::ContextSettings& settings); + const AndroidContext::ContextSettings& settings, + std::shared_ptr io_task_runner); ~AndroidContextDynamicImpeller(); @@ -51,6 +52,7 @@ class AndroidContextDynamicImpeller : public AndroidContext { const AndroidContext::ContextSettings settings_; std::shared_ptr gl_context_; std::shared_ptr vk_context_; + std::shared_ptr io_task_runner_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextDynamicImpeller); }; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc index 79c0ec66bd25b..128fe13042bab 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc @@ -51,7 +51,8 @@ class AndroidContextGLImpeller::ReactorWorker final static std::shared_ptr CreateImpellerContext( const std::shared_ptr& worker, - bool enable_gpu_tracing) { + bool enable_gpu_tracing, + std::shared_ptr io_task_runner) { auto proc_table = std::make_unique( impeller::egl::CreateProcAddressResolver()); @@ -85,11 +86,11 @@ static std::shared_ptr CreateImpellerContext( auto context = impeller::ContextGLES::Create( impeller::Flags{}, std::move(proc_table), is_gles3 ? gles3_shader_mappings : gles2_shader_mappings, - enable_gpu_tracing); + enable_gpu_tracing, std::move(io_task_runner)); #else - auto context = - impeller::ContextGLES::Create(impeller::Flags{}, std::move(proc_table), - gles2_shader_mappings, enable_gpu_tracing); + auto context = impeller::ContextGLES::Create( + impeller::Flags{}, std::move(proc_table), gles2_shader_mappings, + enable_gpu_tracing, std::move(io_task_runner)); #endif // !SLIMPELLER if (!context) { @@ -107,10 +108,12 @@ static std::shared_ptr CreateImpellerContext( AndroidContextGLImpeller::AndroidContextGLImpeller( std::unique_ptr display, - bool enable_gpu_tracing) + bool enable_gpu_tracing, + std::shared_ptr io_task_runner) : AndroidContext(AndroidRenderingAPI::kImpellerOpenGLES), reactor_worker_(std::shared_ptr(new ReactorWorker())), - display_(std::move(display)) { + display_(std::move(display)), + io_task_runner_(std::move(io_task_runner)) { if (!display_ || !display_->IsValid()) { FML_LOG(ERROR) << "Could not create context with invalid EGL display."; return; @@ -174,8 +177,8 @@ AndroidContextGLImpeller::AndroidContextGLImpeller( return; } - auto impeller_context = - CreateImpellerContext(reactor_worker_, enable_gpu_tracing); + auto impeller_context = CreateImpellerContext( + reactor_worker_, enable_gpu_tracing, io_task_runner_); if (!impeller_context) { FML_LOG(ERROR) << "Could not create Impeller context."; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h index e6d68d8444e38..9983a69752e5c 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h +++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CONTEXT_GL_IMPELLER_H_ #include "flutter/fml/macros.h" +#include "flutter/fml/task_runner.h" #include "flutter/impeller/toolkit/egl/display.h" #include "flutter/shell/platform/android/context/android_context.h" @@ -13,8 +14,10 @@ namespace flutter { class AndroidContextGLImpeller : public AndroidContext { public: - AndroidContextGLImpeller(std::unique_ptr display, - bool enable_gpu_tracing); + AndroidContextGLImpeller( + std::unique_ptr display, + bool enable_gpu_tracing, + std::shared_ptr io_task_runner); ~AndroidContextGLImpeller(); @@ -42,6 +45,7 @@ class AndroidContextGLImpeller : public AndroidContext { std::unique_ptr onscreen_context_; std::unique_ptr offscreen_context_; bool is_valid_ = false; + std::shared_ptr io_task_runner_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextGLImpeller); }; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc b/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc index b74c068de093a..ab43170d8331b 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller_unittests.cc @@ -84,8 +84,8 @@ TEST_F(AndroidContextGLImpellerTest, MSAAFirstAttempt) { .WillOnce(Return(ByMove(std::move(second_result)))); ON_CALL(*display, ChooseConfig(_)) .WillByDefault(Return(ByMove(std::unique_ptr()))); - auto context = - std::make_unique(std::move(display), true); + auto context = std::make_unique(std::move(display), + true, nullptr); ASSERT_TRUE(context); } @@ -131,8 +131,8 @@ TEST_F(AndroidContextGLImpellerTest, FallbackForEmulator) { .WillOnce(Return(ByMove(std::move(fourth_result)))); ON_CALL(*display, ChooseConfig(_)) .WillByDefault(Return(ByMove(std::unique_ptr()))); - auto context = - std::make_unique(std::move(display), true); + auto context = std::make_unique(std::move(display), + true, nullptr); ASSERT_TRUE(context); } diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index dfafb6788a7b8..15915dd6b2252 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -106,7 +106,8 @@ static std::shared_ptr CreateAndroidContext( const flutter::TaskRunners& task_runners, AndroidRenderingAPI android_rendering_api, bool enable_opengl_gpu_tracing, - const AndroidContext::ContextSettings& settings) { + const AndroidContext::ContextSettings& settings, + std::shared_ptr io_task_runner) { switch (android_rendering_api) { #if !SLIMPELLER case AndroidRenderingAPI::kSoftware: @@ -121,11 +122,12 @@ static std::shared_ptr CreateAndroidContext( return std::make_unique(settings); case AndroidRenderingAPI::kImpellerOpenGLES: return std::make_unique( - std::make_unique(), - enable_opengl_gpu_tracing); + std::make_unique(), enable_opengl_gpu_tracing, + std::move(io_task_runner)); case AndroidRenderingAPI::kImpellerAutoselect: // Determine if we're using GL or Vulkan. - return std::make_unique(settings); + return std::make_unique( + settings, std::move(io_task_runner)); } FML_UNREACHABLE(); } @@ -143,7 +145,8 @@ PlatformViewAndroid::PlatformViewAndroid( task_runners, rendering_api, delegate.OnPlatformViewGetSettings().enable_opengl_gpu_tracing, - CreateContextSettings(delegate.OnPlatformViewGetSettings()))) {} + CreateContextSettings(delegate.OnPlatformViewGetSettings()), + delegate.OnPlatformViewGetShutdownSafeIOTaskRunner())) {} PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 7cc1366bac68b..e4b3a64564ff2 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -57,6 +57,9 @@ void LoadDartDeferredLibraryError(intptr_t loading_unit_id, bool transient) override {} void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, AssetResolver::AssetResolverType type) override {} + std::shared_ptr OnPlatformViewGetShutdownSafeIOTaskRunner() const override { + return nullptr; + } flutter::Settings settings_; }; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index e9f822223f939..1fdcb42de2585 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -310,6 +310,9 @@ void LoadDartDeferredLibraryError(intptr_t loading_unit_id, bool transient) override {} void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, flutter::AssetResolver::AssetResolverType type) override {} + std::shared_ptr OnPlatformViewGetShutdownSafeIOTaskRunner() const override { + return nullptr; + } flutter::Settings settings_; }; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 8f00a4a449f5d..907e2ff8a35d2 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -125,6 +125,9 @@ void LoadDartDeferredLibraryError(intptr_t loading_unit_id, bool transient) override {} void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, flutter::AssetResolver::AssetResolverType type) override {} + std::shared_ptr OnPlatformViewGetShutdownSafeIOTaskRunner() const override { + return nullptr; + } flutter::Settings settings_; }; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 40be0c6eff884..ff0b5ba433f54 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -113,6 +113,9 @@ void LoadDartDeferredLibraryError(intptr_t loading_unit_id, bool transient) override {} void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, flutter::AssetResolver::AssetResolverType type) override {} + std::shared_ptr OnPlatformViewGetShutdownSafeIOTaskRunner() const override { + return nullptr; + } flutter::Settings settings_; }; diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm index 87cb679e7babb..0852c3055a594 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios_test.mm @@ -53,6 +53,9 @@ void LoadDartDeferredLibraryError(intptr_t loading_unit_id, bool transient) override {} void UpdateAssetResolverByType(std::unique_ptr updated_asset_resolver, flutter::AssetResolver::AssetResolverType type) override {} + std::shared_ptr OnPlatformViewGetShutdownSafeIOTaskRunner() const override { + return nullptr; + } flutter::Settings settings_; }; diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index 075232e231a77..29d75fbf2d81c 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -495,6 +495,7 @@ InferOpenGLPlatformViewCreationCallback( shell.GetTaskRunners(), // task runners std::make_unique( gl_dispatch_table, fbo_reset_after_present, view_embedder, + shell.GetShutdownSafeIOTaskRunner(), impeller_flags), // embedder_surface platform_dispatch_table, // embedder platform dispatch table view_embedder // external view embedder diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface.cc index 029833f7434ba..8e7eef64491d5 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface.cc @@ -19,4 +19,6 @@ sk_sp EmbedderSurface::CreateResourceContext() const { return nullptr; } +void EmbedderSurface::ReleaseResourceContext() const {} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface.h b/engine/src/flutter/shell/platform/embedder/embedder_surface.h index 32873f1a944a5..b00433e356e93 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface.h @@ -26,6 +26,12 @@ class EmbedderSurface { virtual sk_sp CreateResourceContext() const; + /// Release any platform specific resources associated with the graphics + /// context created by `CreateResourceContext`. + /// + /// @see `PlatformView::ReleaseResourceContext` + virtual void ReleaseResourceContext() const; + private: FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurface); }; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc index 1aad505136eb0..bb4cc4005c588 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc @@ -78,6 +78,7 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, std::shared_ptr external_view_embedder, + std::shared_ptr io_task_runner, impeller::Flags impeller_flags) : gl_dispatch_table_(std::move(gl_dispatch_table)), fbo_reset_after_present_(fbo_reset_after_present), @@ -108,7 +109,7 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( impeller_context_ = impeller::ContextGLES::Create( impeller_flags, std::move(gl), shader_mappings, - /*enable_gpu_tracing=*/false); + /*enable_gpu_tracing=*/false, std::move(io_task_runner)); if (!impeller_context_) { FML_LOG(ERROR) << "Could not create Impeller context."; @@ -230,4 +231,10 @@ sk_sp EmbedderSurfaceGLImpeller::CreateResourceContext() return nullptr; } +// |EmbedderSurface| +void EmbedderSurfaceGLImpeller::ReleaseResourceContext() const { + worker_->SetReactionsAllowedOnCurrentThread(false); + gl_dispatch_table_.gl_clear_current_callback(); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h index 598925f44b483..2d420af3dbaae 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h @@ -33,6 +33,7 @@ class EmbedderSurfaceGLImpeller final : public EmbedderSurface, EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, std::shared_ptr external_view_embedder, + std::shared_ptr io_task_runner, impeller::Flags impeller_flags = {}); ~EmbedderSurfaceGLImpeller() override; @@ -85,6 +86,9 @@ class EmbedderSurfaceGLImpeller final : public EmbedderSurface, // |EmbedderSurface| sk_sp CreateResourceContext() const override; + // |EmbedderSurface| + void ReleaseResourceContext() const override; + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceGLImpeller); }; diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc index 712a5287e3924..04f587cdd1ba8 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.cc @@ -168,6 +168,15 @@ sk_sp PlatformViewEmbedder::CreateResourceContext() const { return embedder_surface_->CreateResourceContext(); } +// |PlatformView| +void PlatformViewEmbedder::ReleaseResourceContext() const { + if (embedder_surface_ == nullptr) { + FML_LOG(ERROR) << "Embedder surface was null."; + return; + } + embedder_surface_->ReleaseResourceContext(); +} + // |PlatformView| std::unique_ptr PlatformViewEmbedder::CreateVSyncWaiter() { if (!platform_dispatch_table_.vsync_callback) { diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h index 1b9f500140e66..6f0bc976f6765 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder.h @@ -135,6 +135,9 @@ class PlatformViewEmbedder final : public PlatformView { // |PlatformView| sk_sp CreateResourceContext() const override; + // |PlatformView| + void ReleaseResourceContext() const override; + // |PlatformView| std::unique_ptr CreateVSyncWaiter() override; diff --git a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc index d82265f4353ab..bf6e1b84cc2a2 100644 --- a/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc +++ b/engine/src/flutter/shell/platform/embedder/platform_view_embedder_unittests.cc @@ -104,6 +104,10 @@ class MockDelegate : public PlatformView::Delegate { OnPlatformViewGetSettings, (), (const, override)); + MOCK_METHOD(std::shared_ptr, + OnPlatformViewGetShutdownSafeIOTaskRunner, + (), + (const, override)); }; class MockResponse : public PlatformMessageResponse { diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc index db76a1c082cec..59296dee3df29 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc @@ -39,7 +39,8 @@ TEST(EmbedderSurfaceGLImpellerTest, GLES3ContextHasGLES3Shaders) { StubDispatchTable(/* version */ "OpenGL ES 3.0"); const auto surface = EmbedderSurfaceGLImpeller( gl_dispatch_table, /* fbo_reset_after_present */ false, - /* external_view_embedder */ nullptr); + /* external_view_embedder */ nullptr, + /* io_task_runner */ nullptr); const std::shared_ptr context = surface.CreateImpellerContext(); @@ -61,7 +62,8 @@ TEST(EmbedderSurfaceGLImpellerTest, GLES2ContextDoesNotHaveGLES3Shaders) { StubDispatchTable(/* version */ "OpenGL ES 2.0"); const auto surface = EmbedderSurfaceGLImpeller( gl_dispatch_table, /* fbo_reset_after_present */ false, - /* external_view_embedder */ nullptr); + /* external_view_embedder */ nullptr, + /* io_task_runner */ nullptr); const std::shared_ptr context = surface.CreateImpellerContext(); diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc index 9a7f8fa44ffbf..d6e5ecd73b6ad 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/tests/platform_view_unittest.cc @@ -107,6 +107,11 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { return settings_; } // |flutter::PlatformView::Delegate| + std::shared_ptr + OnPlatformViewGetShutdownSafeIOTaskRunner() const { + return nullptr; + } + // |flutter::PlatformView::Delegate| void OnPlatformViewDispatchPlatformMessage( std::unique_ptr message) { message_ = std::move(message); From 89af7cd8a7f6795fc65185612558575e40137797 Mon Sep 17 00:00:00 2001 From: Faheem Abbas Date: Fri, 24 Jul 2026 02:12:08 +0500 Subject: [PATCH 028/147] Parse AndroidX property in gradle.properties (#188372) Parses `android.useAndroidX` from `gradle.properties` instead of using a raw substring match. This avoids false positives from commented-out lines such as `#android.useAndroidX=true`, and false negatives for valid Gradle syntax such as `android.useAndroidX = true`. This PR intentionally keeps the change scoped to property parsing and does not change the broader behavior for projects that omit the property entirely. Part of #188306 ## Tests ```console ./bin/flutter test packages/flutter_tools/test/general.shard/android/gradle_test.dart ``` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`), or this change does not require documentation updates. - [x] I added new tests to check the change I am making. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported, or this change is not a breaking change. - [x] All existing and new tests are passing. --------- Co-authored-by: jesswrd --- .../flutter_tools/lib/src/android/gradle.dart | 20 +++++++++- .../general.shard/android/gradle_test.dart | 38 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 3468f2dadea8f..238e51230ef45 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -1128,7 +1128,25 @@ bool isAppUsingAndroidX(Directory androidDirectory) { if (!properties.existsSync()) { return false; } - return properties.readAsStringSync().contains('android.useAndroidX=true'); + bool? usesAndroidX; + final androidXRegExp = RegExp(r'^android\.useAndroidX(?:\s*[=:]\s*|\s+)(\S+)'); + for (final String rawLine in properties.readAsLinesSync()) { + final String line = rawLine.trimLeft(); + if (line.isEmpty || line.startsWith('#') || line.startsWith('!')) { + continue; + } + final RegExpMatch? match = androidXRegExp.firstMatch(line); + if (match == null) { + continue; + } + final String value = match.group(1)!.toLowerCase(); + if (value == 'true') { + usesAndroidX = true; + } else if (value == 'false') { + usesAndroidX = false; + } + } + return usesAndroidX ?? false; } /// Returns the APK files for a given [FlutterProject] and [AndroidBuildInfo]. diff --git a/packages/flutter_tools/test/general.shard/android/gradle_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_test.dart index 934523eb3c428..9c5301b264b4d 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_test.dart @@ -631,6 +631,44 @@ flutter: ProcessManager: () => FakeProcessManager.any(), }, ); + + testUsingContext( + 'returns false for commented-out AndroidX properties', + () async { + final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync( + 'flutter_android.', + ); + + androidDirectory + .childFile('gradle.properties') + .writeAsStringSync('#android.useAndroidX=true'); + + expect(isAppUsingAndroidX(androidDirectory), isFalse); + }, + overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }, + ); + + testUsingContext( + 'returns true when AndroidX property has spaces around the separator', + () async { + final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync( + 'flutter_android.', + ); + + androidDirectory + .childFile('gradle.properties') + .writeAsStringSync('android.useAndroidX = true'); + + expect(isAppUsingAndroidX(androidDirectory), isTrue); + }, + overrides: { + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + }, + ); }); group('printHowToConsumeAar', () { From 817671ee42b6d20e8528562060f6c4a80d811f01 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 23 Jul 2026 21:31:04 +0000 Subject: [PATCH 029/147] [flutter_tools] Initialize Cache.flutterRoot at the start of the upgrade_test suite (#189937) Fixes https://github.com/flutter/flutter/issues/189919 --- .../test/commands.shard/permeable/upgrade_test.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index d3340d53b176b..79c90969f1dfe 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(team-tools): Remove when the execution order dependency is resolved. -// See https://github.com/flutter/flutter/issues/189919 -@Tags(['no-shuffle']) -library; - import 'package:args/command_runner.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -29,6 +24,10 @@ import '../../src/fakes.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { + setUpAll(() { + Cache.flutterRoot = getFlutterRoot(); + }); + group('UpgradeCommandRunner', () { final jan12026 = DateTime.utc(2026); From efe7ac6167ef570a8522b3da831f56fbdc9ad61c Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 19:00:12 -0400 Subject: [PATCH 030/147] Roll Skia from 1d8bf9270d8c to 6e9c4687c001 (15 revisions) (#189954) https://skia.googlesource.com/skia.git/+log/1d8bf9270d8c..6e9c4687c001 2026-07-23 jrosengren@microsoft.com [rust_ico] Add Rust-based ICO decoder using image-rs 2026-07-23 thomsmit@google.com [graphite] Move FloatStorageManager to StorageBufferManager 2026-07-23 kjlubick@google.com Fix Bazel build on Windows 2026-07-23 alexisdavidc@google.com [Maintenance] Add a match_override for tryjob machines 2026-07-23 michaelludwig@google.com [graphite] Remove Insertion helper struct 2026-07-23 michaelludwig@google.com [graphite] Exit searchBinding() loop at shading/non-shading boundary 2026-07-23 samfort@microsoft.com Add SkRRect::contains(const SkPoint&) 2026-07-23 michaelludwig@google.com [graphite] Always specialize xfer fns for color spaces 2026-07-23 michaelludwig@google.com [graphite] Add more details to DrawListLayer types 2026-07-23 thomsmit@google.com [graphite] Storage usage flags to rendersteps 2026-07-23 michaelludwig@google.com Reland "[graphite] Trace more DrawPass details" 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from b65fca8cfc87 to afc9d8707b53 (1 revision) 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll Dawn from 85891e8dcf47 to 9b8c24f3b4ee (6 revisions) 2026-07-23 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 3d3c181bdcf2 to 8521722b7ebc (24 revisions) 2026-07-23 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- engine/src/flutter/sky/packages/sky_engine/LICENSE | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 32e5d6de8468e..0bc0e124e8da1 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '1d8bf9270d8cda62f9a5ec82d44318983ff18194', + 'skia_revision': '6e9c4687c00180d5057b033ee6f099558a2842d6', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds diff --git a/engine/src/flutter/sky/packages/sky_engine/LICENSE b/engine/src/flutter/sky/packages/sky_engine/LICENSE index 2bbf8078864c0..c6d60bce87eba 100644 --- a/engine/src/flutter/sky/packages/sky_engine/LICENSE +++ b/engine/src/flutter/sky/packages/sky_engine/LICENSE @@ -18007,6 +18007,13 @@ found in the LICENSE file. -------------------------------------------------------------------------------- skia +Copyright 2025 Google LLC. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +skia + Copyright 2025 Google, LLC Use of this source code is governed by a BSD-style license that can be From 3a1c4698caed58243353136e38746a0363fc85fe Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 23 Jul 2026 23:09:18 +0000 Subject: [PATCH 031/147] [flutter_tools] Do not always wait for the full timeout when running Spotlight to locate Android Studio on macOS (#189952) https://github.com/flutter/flutter/pull/189461 introduced a script in the flutter_tools AndroidStudio class that wraps the Spotlight mdfind command with a timeout. This script had been waiting for the full 3 second timeout even if the mdfind process exited before then. The extra wait showed up in performance benchmarks of tasks that invoke AndroidStudio such as app compilation. See https://github.com/flutter/flutter/issues/189177 --- .../flutter_tools/lib/src/android/android_studio.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart index 5d5ce6461f1cf..03b611d8f004f 100644 --- a/packages/flutter_tools/lib/src/android/android_studio.dart +++ b/packages/flutter_tools/lib/src/android/android_studio.dart @@ -19,7 +19,14 @@ import '../ios/plist_parser.dart'; @visibleForTesting const String kSpotlightMdfindCommand = - 'mdfind \'kMDItemCFBundleIdentifier="com.google.android.studio*"\' & pid=\$!; (sleep 3; kill -9 \$pid 2>/dev/null) & killer=\$!; wait \$pid 2>/dev/null; status=\$?; kill \$killer 2>/dev/null; exit \$status'; + r'( ' + r' for ((i = 0; i < 30; i++)); do ' + r' sleep .1; ' + r' kill -0 $$ || exit 0; ' + r' done; ' + r' kill -9 $$; ' + r') 2>/dev/null & ' + 'exec mdfind \'kMDItemCFBundleIdentifier="com.google.android.studio*"\''; const _androidStudioTitle = 'Android Studio'; const _androidStudioId = 'AndroidStudio'; From 406508a059078f62407df2d854fb16ef183b95f4 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 19:20:23 -0400 Subject: [PATCH 032/147] Roll Dart SDK from 9258584f98b8 to e3fc57eae9eb (7 revisions) (#189949) https://dart.googlesource.com/sdk.git/+log/9258584f98b8..e3fc57eae9eb 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-60.0.dev 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-59.0.dev 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-58.0.dev 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-57.0.dev 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-56.0.dev 2026-07-23 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-55.0.dev 2026-07-22 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-54.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jsimmons@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0bc0e124e8da1..e19ed03b361c0 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '9258584f98b891048c32450dbb4018d4e27e227e', + 'dart_revision': 'e3fc57eae9eb823fd8430f250586d7a1af7d611f', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 162f1d6e33ee5304540869966f2bee62597473d8 Mon Sep 17 00:00:00 2001 From: Lau Ching Jun Date: Thu, 23 Jul 2026 16:34:07 -0700 Subject: [PATCH 033/147] Consolidate AndroidArch and DarwinArch into CpuArch (#189315) Follow up to #189207, this PR removes all usages of AndroidArch and DarwinArch, replacing them with the newly added CpuArch. --- .../lib/src/android/android_device.dart | 32 +-- .../flutter_tools/lib/src/android/gradle.dart | 16 +- .../flutter_tools/lib/src/base/build.dart | 22 +- .../flutter_tools/lib/src/build_info.dart | 224 ++++++++---------- .../lib/src/build_system/targets/android.dart | 6 +- .../lib/src/build_system/targets/common.dart | 8 +- .../targets/deferred_components.dart | 2 +- .../lib/src/build_system/targets/ios.dart | 20 +- .../lib/src/build_system/targets/macos.dart | 22 +- .../lib/src/commands/build_aar.dart | 4 +- .../lib/src/commands/build_apk.dart | 2 +- .../lib/src/commands/build_appbundle.dart | 2 +- .../lib/src/commands/build_ios.dart | 2 +- .../lib/src/commands/build_ios_framework.dart | 2 +- .../src/commands/build_macos_framework.dart | 2 +- .../lib/src/commands/build_swift_package.dart | 4 +- .../flutter_tools/lib/src/ios/devices.dart | 17 +- packages/flutter_tools/lib/src/ios/mac.dart | 6 +- .../native_assets/android/native_assets.dart | 32 +-- .../native_assets/ios/native_assets.dart | 15 +- .../native_assets/macos/native_assets.dart | 15 +- .../src/isolated/native_assets/targets.dart | 28 +-- .../lib/src/macos/build_macos.dart | 12 +- .../flutter_tools/lib/src/macos/xcdevice.dart | 14 +- .../permeable/build_aar_test.dart | 10 +- .../android/android_device_start_test.dart | 2 +- .../android/android_gradle_builder_test.dart | 42 ++-- .../android/build_validation_test.dart | 16 +- .../test/general.shard/base/build_test.dart | 10 +- .../test/general.shard/build_info_test.dart | 61 ++--- .../test/general.shard/ios/devices_test.dart | 40 ++-- .../ios/ios_device_install_test.dart | 3 +- .../ios/ios_device_project_test.dart | 3 +- .../ios_device_start_nonprebuilt_test.dart | 4 +- .../ios/ios_device_start_prebuilt_test.dart | 2 +- .../targets/native_assets_test.dart | 2 +- .../test/general.shard/macos/xcode_test.dart | 38 +-- 37 files changed, 360 insertions(+), 382 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 39410a8b543f7..29cf0aef59a5d 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -549,27 +549,15 @@ class AndroidDevice extends Device { final TargetPlatform devicePlatform = await targetPlatform; var builtPackage = package; - AndroidArch androidArch; - switch (devicePlatform) { - case TargetPlatform.android_arm: - androidArch = AndroidArch.armeabi_v7a; - case TargetPlatform.android_arm64: - androidArch = AndroidArch.arm64_v8a; - case TargetPlatform.android_x64: - androidArch = AndroidArch.x86_64; - case TargetPlatform.android: - case TargetPlatform.darwin: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.ios: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.linux_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.windows_arm64: - case TargetPlatform.windows_x64: - case TargetPlatform.unsupported: + final CpuArch cpuArch = await this.cpuArch; + switch (cpuArch) { + case CpuArch.armv7: + case CpuArch.arm64: + case CpuArch.x64: + break; + case CpuArch.x86: + case CpuArch.riscv64: + case CpuArch.unknown: _logger.printError('Android platforms are only supported.'); return LaunchResult.failed(); } @@ -583,7 +571,7 @@ class AndroidDevice extends Device { target: mainPath ?? 'lib/main.dart', androidBuildInfo: AndroidBuildInfo( debuggingOptions.buildInfo, - targetArchs: [androidArch], + targetArchs: [cpuArch], ), ); // Package has been built, so we can get the updated application ID and diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 238e51230ef45..1262803b94e91 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -148,8 +148,8 @@ Iterable _apkFilesFor(AndroidBuildInfo androidBuildInfo) { final String productFlavor = androidBuildInfo.buildInfo.lowerCasedFlavor ?? ''; final flavorString = productFlavor.isEmpty ? '' : '-$productFlavor'; if (androidBuildInfo.splitPerAbi) { - return androidBuildInfo.targetArchs.map((AndroidArch arch) { - final String abi = arch.archName; + return androidBuildInfo.targetArchs.map((CpuArch arch) { + final String abi = arch.androidArchName; return 'app$flavorString-$abi-$buildType.apk'; }); } @@ -525,7 +525,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ); } else if (androidBuildInfo.targetArchs.isNotEmpty) { final String targetPlatforms = androidBuildInfo.targetArchs - .map((AndroidArch e) => e.platformName) + .map((CpuArch e) => e.androidPlatformName) .join(','); options.add('-Ptarget-platform=$targetPlatforms'); } @@ -677,7 +677,7 @@ class AndroidGradleBuilder implements AndroidBuilder { Future _isAabStrippedOfDebugSymbols( FlutterProject project, String aabPath, - Iterable targetArchs, + Iterable targetArchs, ) async { if (_androidSdk == null) { _logger.printTrace( @@ -744,7 +744,7 @@ class AndroidGradleBuilder implements AndroidBuilder { logger: _logger, analytics: _analytics, ); - final String archName = androidBuildInfo.targetArchs.single.archName; + final String archName = androidBuildInfo.targetArchs.single.androidArchName; final BuildInfo buildInfo = androidBuildInfo.buildInfo; final File aotSnapshot = _fileSystem .directory(buildInfo.codeSizeDirectory) @@ -865,7 +865,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ); } else if (androidBuildInfo.targetArchs.isNotEmpty) { final String targetPlatforms = androidBuildInfo.targetArchs - .map((AndroidArch e) => e.platformName) + .map((CpuArch e) => e.androidPlatformName) .join(','); command.add('-Ptarget-platform=$targetPlatforms'); } @@ -1205,8 +1205,8 @@ Iterable listApkPaths(AndroidBuildInfo androidBuildInfo) { ]; if (androidBuildInfo.splitPerAbi) { return [ - for (final AndroidArch androidArch in androidBuildInfo.targetArchs) - ['app', androidArch.archName, ...apkPartialName].join('-'), + for (final CpuArch cpuArch in androidBuildInfo.targetArchs) + ['app', cpuArch.androidArchName, ...apkPartialName].join('-'), ]; } return [ diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 8c724e68b76d3..d836785258489 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -57,11 +57,15 @@ class GenSnapshot { Future run({ required SnapshotType snapshotType, - DarwinArch? darwinArch, + // TODO(chingjun): The [CpuArch] parameter is only used for iOS builds (to + // select the correct per-architecture gen_snapshot). This architecture + // information should instead be consolidated into [TargetPlatform] so that + // callers do not need to pass it separately. + CpuArch? cpuArch, Iterable additionalArgs = const [], }) { - assert(darwinArch != DarwinArch.armv7); - assert(snapshotType.platform != TargetPlatform.ios || darwinArch != null); + assert(cpuArch != CpuArch.armv7); + assert(snapshotType.platform != TargetPlatform.ios || cpuArch != null); final args = [...additionalArgs]; // iOS and macOS have separate gen_snapshot binaries for each target @@ -70,7 +74,7 @@ class GenSnapshot { Artifact genSnapshotArtifact; if (snapshotType.platform == TargetPlatform.ios || snapshotType.platform == TargetPlatform.darwin) { - genSnapshotArtifact = darwinArch == DarwinArch.arm64 + genSnapshotArtifact = cpuArch == CpuArch.arm64 ? Artifact.genSnapshotArm64 : Artifact.genSnapshotX64; } else { @@ -113,14 +117,14 @@ class AOTSnapshotter { required BuildMode buildMode, required String mainPath, required String outputPath, - DarwinArch? darwinArch, + CpuArch? cpuArch, String? sdkRoot, List extraGenSnapshotOptions = const [], String? splitDebugInfo, required bool dartObfuscation, bool quiet = false, }) async { - assert(platform != TargetPlatform.ios || darwinArch != null); + assert(platform != TargetPlatform.ios || cpuArch != null); if (!_isValidAotPlatform(platform, buildMode)) { _logger.printError('${platform.getName()} does not support AOT compilation.'); @@ -168,7 +172,7 @@ class AOTSnapshotter { // library that the end-developer can link into their app. const frameworkName = 'App.framework'; if (!quiet) { - final String targetArch = darwinArch!.name; + final String targetArch = cpuArch!.darwinArchName; _logger.printStatus('Building $frameworkName for $targetArch...'); } frameworkPath = _fileSystem.path.join(outputPath, frameworkName); @@ -226,7 +230,7 @@ class AOTSnapshotter { // The name of the debug file must contain additional information about // the architecture, since a single build command may produce // multiple debug files. - final String archName = platform.getName(darwinArch: darwinArch); + final String archName = platform.getName(cpuArch: cpuArch); final debugFilename = 'app.$archName.symbols'; final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; if (shouldSplitDebugInfo) { @@ -249,7 +253,7 @@ class AOTSnapshotter { final int genSnapshotExitCode = await _genSnapshot.run( snapshotType: snapshotType, additionalArgs: genSnapshotArgs, - darwinArch: darwinArch, + cpuArch: cpuArch, ); if (genSnapshotExitCode != 0) { _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 8f768c08e1cd7..551da4d7bc108 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -433,11 +433,7 @@ class BuildInfo { class AndroidBuildInfo { const AndroidBuildInfo( this.buildInfo, { - this.targetArchs = const [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - AndroidArch.x86_64, - ], + this.targetArchs = const [.armv7, .arm64, .x64], this.splitPerAbi = false, }); @@ -452,7 +448,7 @@ class AndroidBuildInfo { final bool splitPerAbi; /// The target platforms for the build. - final Iterable targetArchs; + final Iterable targetArchs; } /// A summary of the compilation strategy used for Dart. @@ -632,12 +628,12 @@ enum CpuArch { factory CpuArch.fromName(String name) { return switch (name) { - 'unknown' => CpuArch.unknown, - 'armv7' => CpuArch.armv7, - 'arm64' => CpuArch.arm64, - 'x86' => CpuArch.x86, - 'x64' || 'x86_64' => CpuArch.x64, - 'riscv64' => CpuArch.riscv64, + 'unknown' => .unknown, + 'armv7' => .armv7, + 'arm64' => .arm64, + 'x86' => .x86, + 'x64' || 'x86_64' => .x64, + 'riscv64' => .riscv64, _ => throw Exception('Unsupported CPU arch name "$name"'), }; } @@ -645,11 +641,58 @@ enum CpuArch { /// The [CpuArch] of the given [hostPlatform]. factory CpuArch.fromHostPlatform(HostPlatform hostPlatform) { return switch (hostPlatform) { - .darwin_x64 || .linux_x64 || .windows_x64 => CpuArch.x64, - .darwin_arm64 || .linux_arm64 || .windows_arm64 => CpuArch.arm64, - .linux_riscv64 => CpuArch.riscv64, + .darwin_x64 || .linux_x64 || .windows_x64 => .x64, + .darwin_arm64 || .linux_arm64 || .windows_arm64 => .arm64, + .linux_riscv64 => .riscv64, }; } + + /// Returns the Dart SDK's name for the specified target architecture. + /// + /// When building for Darwin platforms, the tool invokes architecture-specific + /// variants of `gen_snapshot`, one for each target architecture. The output + /// instructions are then built into architecture-specific binaries, which are + /// merged into a universal binary using the `lipo` tool. + String get dartName { + return switch (this) { + armv7 => 'armv7', + arm64 => 'arm64', + x86 => 'x86', + x64 => 'x64', + riscv64 => 'riscv64', + unknown => throw UnsupportedError('Unexpected CPU arch $this'), + }; + } + + /// The Apple architecture name for this architecture. + /// + /// This is the name understood by the Darwin toolchain (e.g. `lipo`, `clang`, + /// and the `-arch` flag) and used for architecture-specific build output + /// directories on iOS and macOS. This differs from [dartName] for [x64], + /// which maps to `x86_64` here. + String get darwinArchName => switch (this) { + armv7 => 'armv7', + arm64 => 'arm64', + x64 => 'x86_64', + x86 || riscv64 || unknown => throw UnsupportedError('Unexpected Darwin CPU arch $this'), + }; + + /// The name of the Android ABI (as used in `jniLibs` directories) for this + /// architecture. + String get androidArchName => switch (this) { + armv7 => 'armeabi-v7a', + arm64 => 'arm64-v8a', + x64 => 'x86_64', + x86 || riscv64 || unknown => throw UnsupportedError('Unexpected Android CPU arch $this'), + }; + + /// The `TargetPlatform` name of the Android platform for this architecture. + String get androidPlatformName => switch (this) { + armv7 => 'android-arm', + arm64 => 'android-arm64', + x64 => 'android-x64', + x86 || riscv64 || unknown => throw UnsupportedError('Unexpected Android CPU arch $this'), + }; } enum TargetPlatform { @@ -667,8 +710,7 @@ enum TargetPlatform { web_javascript('web-javascript'), // The arch specific android target platforms are soft-deprecated. // Instead of using TargetPlatform as a combination arch + platform - // the code will be updated to carry arch information in [DarwinArch] - // and [AndroidArch]. + // the code will be updated to carry arch information in [CpuArch]. android_arm('android-arm'), android_arm64('android-arm64'), android_x64('android-x64'), @@ -701,10 +743,10 @@ enum TargetPlatform { final String _defaultName; - String getName({DarwinArch? darwinArch}) { + String getName({CpuArch? cpuArch}) { return switch (this) { - TargetPlatform.ios when darwinArch != null => 'ios-${darwinArch.name}', - TargetPlatform.darwin when darwinArch != null => 'darwin-${darwinArch.name}', + TargetPlatform.ios when cpuArch != null => 'ios-${cpuArch.darwinArchName}', + TargetPlatform.darwin when cpuArch != null => 'darwin-${cpuArch.darwinArchName}', _ => _defaultName, }; } @@ -760,136 +802,74 @@ enum TargetPlatform { throw UnsupportedError('Target platform is unsupported.'); } -/// iOS and macOS target device architecture. -// -// TODO(cbracken): split TargetPlatform.ios into ios_armv7, ios_arm64. -enum DarwinArch { - armv7, // Deprecated. Used to display 32-bit unsupported devices. - arm64, - x86_64; - - /// Returns the Dart SDK's name for the specified target architecture. - /// - /// When building for Darwin platforms, the tool invokes architecture-specific - /// variants of `gen_snapshot`, one for each target architecture. The output - /// instructions are then built into architecture-specific binaries, which are - /// merged into a universal binary using the `lipo` tool. - String get dartName { - return switch (this) { - armv7 => 'armv7', - arm64 => 'arm64', - x86_64 => 'x64', - }; - } -} - -// TODO(zanderso): replace all android TargetPlatform usage with AndroidArch. -enum AndroidArch { - armeabi_v7a, - arm64_v8a, - x86_64; - - String get archName => switch (this) { - armeabi_v7a => 'armeabi-v7a', - arm64_v8a => 'arm64-v8a', - x86_64 => 'x86_64', - }; - - String get platformName => switch (this) { - armeabi_v7a => 'android-arm', - arm64_v8a => 'android-arm64', - x86_64 => 'android-x64', - }; -} - /// The default set of iOS device architectures to build for. -List defaultIOSArchsForEnvironment( - EnvironmentType environmentType, - Artifacts artifacts, -) { +List defaultIOSArchsForEnvironment(EnvironmentType environmentType, Artifacts artifacts) { // Handle single-arch local engines. final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo; if (localEngineInfo != null) { final String localEngineName = localEngineInfo.localTargetName; if (localEngineName.contains('_arm64')) { - return [DarwinArch.arm64]; + return [.arm64]; } if (localEngineName.contains('_sim')) { - return [DarwinArch.x86_64]; + return [.x64]; } } else if (environmentType == EnvironmentType.simulator) { - return [DarwinArch.x86_64, DarwinArch.arm64]; + return [.x64, .arm64]; } - return [DarwinArch.arm64]; + return [.arm64]; } /// The default set of macOS device architectures to build for. -List defaultMacOSArchsForEnvironment(Artifacts artifacts) { +List defaultMacOSArchsForEnvironment(Artifacts artifacts) { // Handle single-arch local engines. final LocalEngineInfo? localEngineInfo = artifacts.localEngineInfo; if (localEngineInfo != null) { if (localEngineInfo.localTargetName.contains('_arm64')) { - return [DarwinArch.arm64]; + return [.arm64]; } - return [DarwinArch.x86_64]; + return [.x64]; } - return [DarwinArch.x86_64, DarwinArch.arm64]; + return [.x64, .arm64]; } -DarwinArch getIOSArchForName(String arch) { - switch (arch) { - case 'armv7': - case 'armv7f': // iPhone 4S. - case 'armv7s': // iPad 4. - return DarwinArch.armv7; - case 'arm64': - case 'arm64e': // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices. - return DarwinArch.arm64; - case 'x86_64': - return DarwinArch.x86_64; - } - throw Exception('Unsupported iOS arch name "$arch"'); -} - -DarwinArch getDarwinArchForName(String arch) { - return switch (arch) { - 'arm64' => DarwinArch.arm64, - 'x86_64' => DarwinArch.x86_64, - _ => throw Exception('Unsupported MacOS arch name "$arch"'), +/// Returns the [CpuArch] for the given architecture or platform [name]. +/// +/// This accepts the various naming conventions used across platforms: +/// * Apple architecture names (e.g. `armv7`, `arm64`, `arm64e`, `x86_64`). +/// * Android target platform names (e.g. `android-arm`, `android-arm64`, +/// `android-x64`). +/// +/// Consolidating these into a single lookup is safe because the accepted names +/// do not overlap, so callers on any platform get the expected result. +CpuArch getCpuArchForName(String name) { + return switch (name) { + 'armv7' || + 'armv7f' || // iPhone 4S. + 'armv7s' || // iPad 4. + 'android-arm' => .armv7, + 'arm64' || + 'arm64e' || // iPhone XS/XS Max/XR and higher. arm64 runs on arm64e devices. + 'android-arm64' => .arm64, + 'x86_64' || 'android-x64' => .x64, + _ => throw Exception('Unsupported CPU arch name "$name"'), }; } -List getDarwinArchsFromEnv(Map defines) { - const defaultDarwinArchitectures = [DarwinArch.x86_64, DarwinArch.arm64]; - return defines[kDarwinArchs]?.split(' ').map(getDarwinArchForName).toList() ?? +/// The set of Darwin (iOS/macOS) architectures configured in [defines], or a +/// default of x86_64 and arm64 if unspecified. +List getCpuArchsFromEnv(Map defines) { + const defaultDarwinArchitectures = [.x64, .arm64]; + return defines[kDarwinArchs]?.split(' ').map(getCpuArchForName).toList() ?? defaultDarwinArchitectures; } -AndroidArch getAndroidArchForName(String platform) { - return switch (platform) { - 'android-arm' => AndroidArch.armeabi_v7a, - 'android-arm64' => AndroidArch.arm64_v8a, - 'android-x64' => AndroidArch.x86_64, - _ => throw Exception('Unsupported Android arch name "$platform"'), - }; -} - -DarwinArch getCurrentDarwinArch() { - return switch (globals.os.hostPlatform) { - HostPlatform.darwin_arm64 => DarwinArch.arm64, - HostPlatform.darwin_x64 => DarwinArch.x86_64, - final HostPlatform unsupported => throw Exception( - 'Unsupported Darwin host platform "$unsupported"', - ), - }; -} - HostPlatform getCurrentHostPlatform() { if (globals.platform.isMacOS) { - return switch (getCurrentDarwinArch()) { - DarwinArch.arm64 => HostPlatform.darwin_arm64, - DarwinArch.x86_64 => HostPlatform.darwin_x64, - DarwinArch.armv7 => throw Exception('Unsupported macOS arch "amv7"'), + return switch (globals.os.hostPlatform) { + HostPlatform.darwin_arm64 => .darwin_arm64, + HostPlatform.darwin_x64 => .darwin_x64, + _ => throw Exception('Unsupported Darwin host platform "${globals.os.hostPlatform}"'), }; } if (globals.platform.isLinux) { @@ -1242,8 +1222,8 @@ String? _uncapitalize(String? s) { // flutter_ignore: deprecation_syntax (see analyze.dart) @Deprecated('Use TargetPlatform.getName() instead') -String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch}) { - return platform.getName(darwinArch: darwinArch); +String getNameForTargetPlatform(TargetPlatform platform, {CpuArch? cpuArch}) { + return platform.getName(cpuArch: cpuArch); } // flutter_ignore: deprecation_syntax (see analyze.dart) diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index 7aa9f8fb871a1..76c2c31fc15dc 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -177,7 +177,7 @@ class AndroidAot extends AotElfBase { /// The name of the produced Android ABI. String get _androidAbiName { - return getAndroidArchForName(targetPlatform.getName()).archName; + return getCpuArchForName(targetPlatform.getName()).androidArchName; } @override @@ -304,7 +304,7 @@ class AndroidAotBundle extends Target { /// The name of the produced Android ABI. String get _androidAbiName { - return getAndroidArchForName(dependency.targetPlatform.getName()).archName; + return getCpuArchForName(dependency.targetPlatform.getName()).androidArchName; } @override @@ -382,7 +382,7 @@ class AndroidAotDeferredComponentsBundle extends Target { /// The name of the produced Android ABI. String get _androidAbiName { - return getAndroidArchForName(dependency.targetPlatform.getName()).archName; + return getCpuArchForName(dependency.targetPlatform.getName()).androidArchName; } @override 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 692bc456838cb..b66db673bcc2e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -500,7 +500,7 @@ abstract final class Lipo { /// Otherwise, `lipo` would fail if the given paths didn't exist. static Future create( Environment environment, - List darwinArchs, { + List cpuArchs, { required String relativePath, required String inputDir, bool skipMissingInputs = false, @@ -511,9 +511,9 @@ abstract final class Lipo { ); environment.fileSystem.directory(resultPath).parent.createSync(recursive: true); - Iterable inputPaths = darwinArchs.map( - (DarwinArch iosArch) => - environment.fileSystem.path.join(inputDir, iosArch.name, relativePath), + Iterable inputPaths = cpuArchs.map( + (CpuArch cpuArch) => + environment.fileSystem.path.join(inputDir, cpuArch.darwinArchName, relativePath), ); if (skipMissingInputs) { inputPaths = inputPaths.where(environment.fileSystem.isFileSync); diff --git a/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart b/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart index 0bd5daac4e10c..8dfdc16884247 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart @@ -39,7 +39,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { return [ for (final AndroidAotDeferredComponentsBundle target in deferredComponentsDependencies) if (deferredComponentsTargets.contains(target.name)) - getAndroidArchForName(target.dependency.targetPlatform.getName()).archName, + getCpuArchForName(target.dependency.targetPlatform.getName()).androidArchName, ]; } 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 82a470f2b721e..9535489b8f46d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -72,9 +72,9 @@ abstract class AotAssemblyBase extends Target { final targetPlatform = TargetPlatform.fromName(environmentTargetPlatform); final String? splitDebugInfo = environment.defines[kSplitDebugInfo]; final dartObfuscation = environment.defines[kDartObfuscation] == 'true'; - final List darwinArchs = - environment.defines[kIosArchs]?.split(' ').map(getIOSArchForName).toList() ?? - [DarwinArch.arm64]; + final List cpuArchs = + environment.defines[kIosArchs]?.split(' ').map(getCpuArchForName).toList() ?? + [CpuArch.arm64]; if (targetPlatform != TargetPlatform.ios) { throw Exception('aot_assembly is only supported for iOS applications.'); } @@ -94,15 +94,15 @@ abstract class AotAssemblyBase extends Target { // If we're building multiple iOS archs the binaries need to be lipo'd // together. final pending = >[]; - for (final darwinArch in darwinArchs) { + for (final cpuArch in cpuArchs) { final archExtraGenSnapshotOptions = List.of(extraGenSnapshotOptions); if (codeSizeDirectory != null) { final File codeSizeFile = environment.fileSystem .directory(codeSizeDirectory) - .childFile('snapshot.${darwinArch.name}.json'); + .childFile('snapshot.${cpuArch.darwinArchName}.json'); final File precompilerTraceFile = environment.fileSystem .directory(codeSizeDirectory) - .childFile('trace.${darwinArch.name}.json'); + .childFile('trace.${cpuArch.darwinArchName}.json'); archExtraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); archExtraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}'); } @@ -111,8 +111,8 @@ abstract class AotAssemblyBase extends Target { platform: targetPlatform, buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, - outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name), - darwinArch: darwinArch, + outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), + cpuArch: cpuArch, sdkRoot: sdkRoot, quiet: true, splitDebugInfo: splitDebugInfo, @@ -129,7 +129,7 @@ abstract class AotAssemblyBase extends Target { // Combine the app lib into a fat framework. await Lipo.create( environment, - darwinArchs, + cpuArchs, relativePath: 'App.framework/App', inputDir: buildOutputPath, ); @@ -137,7 +137,7 @@ abstract class AotAssemblyBase extends Target { // And combine the dSYM for each architecture too, if it was created. await Lipo.create( environment, - darwinArchs, + cpuArchs, relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App', inputDir: buildOutputPath, // Don't fail if the dSYM wasn't created (i.e. during a debug build). 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 c0f2923a5938e..e42533a8ddfba 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -210,10 +210,10 @@ class DebugMacOSFramework extends Target { environment.fileSystem.path.join(environment.buildDir.path, 'App.framework', 'App'), ); - final Iterable darwinArchs = getDarwinArchsFromEnv(environment.defines); + final Iterable cpuArchs = getCpuArchsFromEnv(environment.defines); - final Iterable darwinArchArguments = darwinArchs.expand( - (DarwinArch arch) => ['-arch', arch.name], + final Iterable darwinArchArguments = cpuArchs.expand( + (CpuArch arch) => ['-arch', arch.darwinArchName], ); outputFile.createSync(recursive: true); @@ -287,7 +287,7 @@ class CompileMacOSFramework extends Target { kExtraGenSnapshotOptions, ); final targetPlatform = TargetPlatform.fromName(targetPlatformEnvironment); - final List darwinArchs = getDarwinArchsFromEnv(environment.defines); + final List cpuArchs = getCpuArchsFromEnv(environment.defines); if (targetPlatform != TargetPlatform.darwin) { throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.'); } @@ -301,14 +301,14 @@ class CompileMacOSFramework extends Target { ); final pending = >[]; - for (final darwinArch in darwinArchs) { + for (final cpuArch in cpuArchs) { if (codeSizeDirectory != null) { final File codeSizeFile = environment.fileSystem .directory(codeSizeDirectory) - .childFile('snapshot.${darwinArch.name}.json'); + .childFile('snapshot.${cpuArch.darwinArchName}.json'); final File precompilerTraceFile = environment.fileSystem .directory(codeSizeDirectory) - .childFile('trace.${darwinArch.name}.json'); + .childFile('trace.${cpuArch.darwinArchName}.json'); extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}'); } @@ -320,9 +320,9 @@ class CompileMacOSFramework extends Target { snapshotter.build( buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, - outputPath: environment.fileSystem.path.join(buildOutputPath, darwinArch.name), + outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), platform: TargetPlatform.darwin, - darwinArch: darwinArch, + cpuArch: cpuArch, splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, extraGenSnapshotOptions: extraGenSnapshotOptions, @@ -339,7 +339,7 @@ class CompileMacOSFramework extends Target { // Combine the app lib into a fat framework. await Lipo.create( environment, - darwinArchs, + cpuArchs, relativePath: 'App.framework/App', inputDir: buildOutputPath, ); @@ -347,7 +347,7 @@ class CompileMacOSFramework extends Target { // And combine the dSYM for each architecture too, if it was created. await Lipo.create( environment, - darwinArchs, + cpuArchs, relativePath: 'App.framework.dSYM/Contents/Resources/DWARF/App', inputDir: buildOutputPath, // Don't fail if the dSYM wasn't created (i.e. during a debug build). diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index 67561f9ce1049..ef719e6e7271b 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -118,9 +118,9 @@ class BuildAarCommand extends BuildSubCommand { } final androidBuildInfo = {}; - final Iterable targetArchitectures = stringsArg( + final Iterable targetArchitectures = stringsArg( 'target-platform', - ).map(getAndroidArchForName); + ).map(getCpuArchForName); final String? buildNumberArg = stringArg('build-number'); final String buildNumber = diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 184a68be58d90..824a1008a01a6 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -124,7 +124,7 @@ class BuildApkCommand extends BuildSubCommand { final androidBuildInfo = AndroidBuildInfo( buildInfo, splitPerAbi: boolArg('split-per-abi'), - targetArchs: _targetArchs.map(getAndroidArchForName), + targetArchs: _targetArchs.map(getCpuArchForName), ); validateBuild(androidBuildInfo); globals.terminal.usesTerminalUi = true; diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index af58cbe68fa11..f96180990545f 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -120,7 +120,7 @@ class BuildAppBundleCommand extends BuildSubCommand { } final androidBuildInfo = AndroidBuildInfo( await getBuildInfo(), - targetArchs: stringsArg('target-platform').map(getAndroidArchForName), + targetArchs: stringsArg('target-platform').map(getCpuArchForName), ); // Do all setup verification that doesn't involve loading units. Checks that // require generated loading units are done after gen_snapshot in assemble. diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index e783f793b9e7a..5131df96364ac 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -1022,7 +1022,7 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { appFilenamePattern: 'App', ); // Only support 64bit iOS code size analysis. - final String arch = DarwinArch.arm64.name; + final String arch = CpuArch.arm64.darwinArchName; final File aotSnapshot = globals.fs .directory(buildInfo.codeSizeDirectory) .childFile('snapshot.$arch.json'); 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 84e6b7a28d870..432e9519afc37 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -802,7 +802,7 @@ end kIosArchs: defaultIOSArchsForEnvironment( sdkType, globals.artifacts!, - ).map((DarwinArch e) => e.name).join(' '), + ).map((CpuArch e) => e.darwinArchName).join(' '), kSdkRoot: await globals.xcode!.sdkLocation(sdkType), ...buildInfo.toBuildSystemEnvironment(), }, 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 e4167b526188f..52cf414c511df 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos_framework.dart @@ -261,7 +261,7 @@ end kTargetPlatform: TargetPlatform.darwin.getName(), kDarwinArchs: defaultMacOSArchsForEnvironment( globals.artifacts!, - ).map((DarwinArch e) => e.name).join(' '), + ).map((CpuArch e) => e.darwinArchName).join(' '), ...buildInfo.toBuildSystemEnvironment(), }, artifacts: globals.artifacts!, diff --git a/packages/flutter_tools/lib/src/commands/build_swift_package.dart b/packages/flutter_tools/lib/src/commands/build_swift_package.dart index 85cdc520a7fbf..ba41167f26291 100644 --- a/packages/flutter_tools/lib/src/commands/build_swift_package.dart +++ b/packages/flutter_tools/lib/src/commands/build_swift_package.dart @@ -1413,14 +1413,14 @@ class AppFrameworkAndNativeAssetsDependencies { kIosArchs: defaultIOSArchsForEnvironment( sdk.sdkType, _utils.artifacts, - ).map((DarwinArch e) => e.name).join(' '), + ).map((CpuArch e) => e.darwinArchName).join(' '), kSdkRoot: await _utils.xcode.sdkLocation(sdk.sdkType), }; case FlutterDarwinPlatform.macos: return { kDarwinArchs: defaultMacOSArchsForEnvironment( _utils.artifacts, - ).map((DarwinArch e) => e.name).join(' '), + ).map((CpuArch e) => e.darwinArchName).join(' '), }; } } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index c131edc8a8bdd..48f9fa1ebdb70 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -309,7 +309,7 @@ class IOSDevice extends Device { super.id, { required FileSystem fileSystem, required this.name, - required this.cpuArchitecture, + required CpuArch cpuArch, required this.connectionInterface, required this.isConnected, required this.isPaired, @@ -325,7 +325,8 @@ class IOSDevice extends Device { required IProxy iProxy, required super.logger, required Analytics analytics, - }) : _sdkVersion = sdkVersion, + }) : _cpuArch = cpuArch, + _sdkVersion = sdkVersion, _iosDeploy = iosDeploy, _iMobileDevice = iMobileDevice, _coreDeviceControl = coreDeviceControl, @@ -370,14 +371,10 @@ class IOSDevice extends Device { @override bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease; - final DarwinArch cpuArchitecture; + final CpuArch _cpuArch; @override - Future get cpuArch async => switch (cpuArchitecture) { - .armv7 => CpuArch.armv7, - .arm64 => CpuArch.arm64, - .x86_64 => CpuArch.x64, - }; + Future get cpuArch async => _cpuArch; @override /// The [connectionInterface] provided from `XCDevice.getAvailableIOSDevices` @@ -502,7 +499,7 @@ class IOSDevice extends Device { @override // 32-bit devices are not supported. - Future isSupported() async => cpuArchitecture == DarwinArch.arm64; + Future isSupported() async => _cpuArch == .arm64; @override Future startApp( @@ -535,7 +532,7 @@ class IOSDevice extends Device { app: package as BuildableIOSApp, buildInfo: debuggingOptions.buildInfo, targetOverride: mainPath, - activeArch: cpuArchitecture, + activeArch: _cpuArch, deviceID: id, disablePortPublication: debuggingOptions.usingCISystem && debuggingOptions.disablePortPublication, diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 31d03819723a8..4a4fa844df7f6 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -138,7 +138,7 @@ Future buildXcodeProject({ required BuildInfo buildInfo, String? targetOverride, EnvironmentType environmentType = EnvironmentType.physical, - DarwinArch? activeArch, + CpuArch? activeArch, bool codesign = true, String? deviceID, bool configOnly = false, @@ -463,10 +463,10 @@ Future buildXcodeProject({ if (!hasWatchCompanion) { // ONLY_ACTIVE_ARCH specifies whether the product includes only code for // the native architecture. - final onlyActiveArch = activeArch == getCurrentDarwinArch(); + final onlyActiveArch = activeArch == CpuArch.fromHostPlatform(getCurrentHostPlatform()); buildCommands.add('ONLY_ACTIVE_ARCH=${onlyActiveArch ? 'YES' : 'NO'}'); - buildCommands.add('ARCHS=${activeArch.name}'); + buildCommands.add('ARCHS=${activeArch.darwinArchName}'); } } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart index ae6ffae111847..4d63584ad24a5 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/android/native_assets.dart @@ -24,7 +24,8 @@ Future> copyNativeCodeAssetsAndroid( assert(assetTargetLocations.isNotEmpty); final installedFiles = []; final jniArchDirs = [ - for (final AndroidArch androidArch in AndroidArch.values) androidArch.archName, + for (final CpuArch cpuArch in [CpuArch.armv7, CpuArch.arm64, CpuArch.x64]) + cpuArch.androidArchName, ]; for (final jniArchDir in jniArchDirs) { final Uri archUri = targetUri.resolve('jniLibs/lib/$jniArchDir/'); @@ -33,8 +34,8 @@ Future> copyNativeCodeAssetsAndroid( for (final MapEntry assetMapping in assetTargetLocations.entries) { final Uri source = assetMapping.key.codeAsset.file!; final Uri target = (assetMapping.value.path as KernelAssetAbsolutePath).uri; - final AndroidArch androidArch = _getAndroidArch(assetMapping.value.target.architecture); - final String jniArchDir = androidArch.archName; + final CpuArch cpuArch = _getAndroidArch(assetMapping.value.target.architecture); + final String jniArchDir = cpuArch.androidArchName; final Uri archUri = targetUri.resolve('jniLibs/lib/$jniArchDir/'); final Uri assetTargetUri = archUri.resolveUri(target); final String targetFullPath = assetTargetUri.toFilePath(); @@ -44,21 +45,24 @@ Future> copyNativeCodeAssetsAndroid( return installedFiles; } -/// Get the [Architecture] for [androidArch]. -Architecture getNativeAndroidArchitecture(AndroidArch androidArch) { - return switch (androidArch) { - AndroidArch.armeabi_v7a => Architecture.arm, - AndroidArch.arm64_v8a => Architecture.arm64, - AndroidArch.x86_64 => Architecture.x64, +/// Get the [Architecture] for [cpuArch]. +Architecture getNativeAndroidArchitecture(CpuArch cpuArch) { + return switch (cpuArch) { + CpuArch.armv7 => Architecture.arm, + CpuArch.arm64 => Architecture.arm64, + CpuArch.x64 => Architecture.x64, + CpuArch.x86 || + CpuArch.riscv64 || + CpuArch.unknown => throwToolExit('Invalid Android arch: $cpuArch.'), }; } -/// Get the [AndroidArch] for [architecture]. -AndroidArch _getAndroidArch(Architecture architecture) { +/// Get the [CpuArch] for [architecture]. +CpuArch _getAndroidArch(Architecture architecture) { return switch (architecture) { - Architecture.arm => AndroidArch.armeabi_v7a, - Architecture.arm64 => AndroidArch.arm64_v8a, - Architecture.x64 => AndroidArch.x86_64, + Architecture.arm => CpuArch.armv7, + Architecture.arm64 => CpuArch.arm64, + Architecture.x64 => CpuArch.x64, Architecture.riscv64 => throwToolExit('Android RISC-V not yet supported.'), _ => throwToolExit('Invalid architecture: $architecture.'), }; diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart index 7eb1f1e049b4b..68680a9209599 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/ios/native_assets.dart @@ -20,12 +20,15 @@ IOSSdk getIOSSdk(EnvironmentType environmentType) { }; } -/// Extract the [Architecture] from a [DarwinArch]. -Architecture getNativeIOSArchitecture(DarwinArch darwinArch) { - return switch (darwinArch) { - DarwinArch.armv7 => Architecture.arm, - DarwinArch.arm64 => Architecture.arm64, - DarwinArch.x86_64 => Architecture.x64, +/// Extract the [Architecture] from a [CpuArch]. +Architecture getNativeIOSArchitecture(CpuArch cpuArch) { + return switch (cpuArch) { + CpuArch.armv7 => Architecture.arm, + CpuArch.arm64 => Architecture.arm64, + CpuArch.x64 => Architecture.x64, + CpuArch.x86 || + CpuArch.riscv64 || + CpuArch.unknown => throw Exception('Unknown iOS CPU arch: $cpuArch.'), }; } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart index 2824681aea1f9..4a1d0640a2280 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/macos/native_assets.dart @@ -13,12 +13,15 @@ import 'native_assets_host.dart'; // TODO(dcharkes): Fetch minimum MacOS version from somewhere. https://github.com/flutter/flutter/issues/145104 const targetMacOSVersion = 13; -/// Extract the [Architecture] from a [DarwinArch]. -Architecture getNativeMacOSArchitecture(DarwinArch darwinArch) { - return switch (darwinArch) { - DarwinArch.arm64 => Architecture.arm64, - DarwinArch.x86_64 => Architecture.x64, - DarwinArch.armv7 => throw Exception('Unknown DarwinArch: $darwinArch.'), +/// Extract the [Architecture] from a [CpuArch]. +Architecture getNativeMacOSArchitecture(CpuArch cpuArch) { + return switch (cpuArch) { + CpuArch.arm64 => Architecture.arm64, + CpuArch.x64 => Architecture.x64, + CpuArch.armv7 || + CpuArch.x86 || + CpuArch.riscv64 || + CpuArch.unknown => throw Exception('Unknown macOS CPU arch: $cpuArch.'), }; } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart index 5c8b595fb79c5..afe0ed360744e 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart @@ -10,14 +10,12 @@ import 'package:hooks/hooks.dart'; import '../../base/common.dart' show throwToolExit; import '../../build_info.dart' show - AndroidArch, BuildMode, - DarwinArch, + CpuArch, EnvironmentType, TargetPlatform, - getAndroidArchForName, - getDarwinArchsFromEnv, - getIOSArchForName, + getCpuArchForName, + getCpuArchsFromEnv, kAndroidArchs, kIosArchs, kSdkRoot; @@ -136,7 +134,7 @@ sealed class AssetBuildTarget { Map environmentDefines, List supportedAssetTypes, ) { - return getDarwinArchsFromEnv(environmentDefines) + return getCpuArchsFromEnv(environmentDefines) .map(getNativeMacOSArchitecture) .map( (Architecture architecture) => MacOSAssetTarget( @@ -169,10 +167,10 @@ sealed class AssetBuildTarget { FileSystem fileSystem, List supportedAssetTypes, ) { - final List iosArchitectures = - _emptyToNull(environmentDefines[kIosArchs])?.split(' ').map(getIOSArchForName).toList() ?? - [DarwinArch.arm64]; - return iosArchitectures + final List cpuArchs = + _emptyToNull(environmentDefines[kIosArchs])?.split(' ').map(getCpuArchForName).toList() ?? + [CpuArch.arm64]; + return cpuArchs .map(getNativeIOSArchitecture) .map( (Architecture architecture) => IOSAssetTarget( @@ -420,19 +418,19 @@ final class FlutterTesterAssetTarget extends CodeAssetTarget { subtarget.setCCompilerConfig(mustMatchAppBuild: false); } -List _androidArchs(TargetPlatform targetPlatform, String? androidArchsEnvironment) { +List _androidArchs(TargetPlatform targetPlatform, String? androidArchsEnvironment) { switch (targetPlatform) { case TargetPlatform.android_arm: - return [AndroidArch.armeabi_v7a]; + return [CpuArch.armv7]; case TargetPlatform.android_arm64: - return [AndroidArch.arm64_v8a]; + return [CpuArch.arm64]; case TargetPlatform.android_x64: - return [AndroidArch.x86_64]; + return [CpuArch.x64]; case TargetPlatform.android: if (androidArchsEnvironment == null) { throw MissingDefineException(kAndroidArchs, 'native_assets'); } - return androidArchsEnvironment.split(' ').map(getAndroidArchForName).toList(); + return androidArchsEnvironment.split(' ').map(getCpuArchForName).toList(); case TargetPlatform.darwin: case TargetPlatform.fuchsia_arm64: case TargetPlatform.fuchsia_x64: diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 6c9a957f28da5..be2f85f45842f 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -378,11 +378,11 @@ Future _writeCodeSizeAnalysis(BuildInfo buildInfo, SizeAnalyzer? sizeAnaly if (buildInfo.codeSizeDirectory == null || sizeAnalyzer == null) { return; } - final File? aotSnapshot = DarwinArch.values - .map((DarwinArch arch) { + final File? aotSnapshot = const [CpuArch.armv7, CpuArch.arm64, CpuArch.x64] + .map((CpuArch arch) { return globals.fs .directory(buildInfo.codeSizeDirectory) - .childFile('snapshot.${arch.name}.json'); + .childFile('snapshot.${arch.darwinArchName}.json'); // Pick the first if there are multiple for simplicity }) .firstWhere((File? file) => file!.existsSync(), orElse: () => null); @@ -391,11 +391,11 @@ Future _writeCodeSizeAnalysis(BuildInfo buildInfo, SizeAnalyzer? sizeAnaly 'No code size snapshot file (snapshot..json) found in ${buildInfo.codeSizeDirectory}', ); } - final File? precompilerTrace = DarwinArch.values - .map((DarwinArch arch) { + final File? precompilerTrace = const [CpuArch.armv7, CpuArch.arm64, CpuArch.x64] + .map((CpuArch arch) { return globals.fs .directory(buildInfo.codeSizeDirectory) - .childFile('trace.${arch.name}.json'); + .childFile('trace.${arch.darwinArchName}.json'); }) .firstWhere((File? file) => file!.existsSync(), orElse: () => null); if (precompilerTrace == null) { diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart index b5cbeeaa9b448..403e292a87768 100644 --- a/packages/flutter_tools/lib/src/macos/xcdevice.dart +++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart @@ -632,7 +632,7 @@ class XCDevice { deviceMap[identifier] = IOSDevice( identifier, name: name, - cpuArchitecture: _cpuArchitecture(device), + cpuArch: _cpuArchitecture(device), connectionInterface: connectionInterface, isConnected: isConnected, sdkVersion: sdkVersionString, @@ -724,21 +724,21 @@ class XCDevice { return null; } - DarwinArch _cpuArchitecture(Map deviceProperties) { - DarwinArch? cpuArchitecture; + CpuArch _cpuArchitecture(Map deviceProperties) { + CpuArch? cpuArchitecture; final Object? architecture = deviceProperties['architecture']; if (architecture is String) { try { - cpuArchitecture = getIOSArchForName(architecture); + cpuArchitecture = getCpuArchForName(architecture); } on Exception { // Fallback to default iOS architecture. Future-proof against a // theoretical version of Xcode that changes this string to something // slightly different like "ARM64", or armv7 variations like // armv7s and armv7f. if (architecture.startsWith('armv7')) { - cpuArchitecture = DarwinArch.armv7; + cpuArchitecture = CpuArch.armv7; } else { - cpuArchitecture = DarwinArch.arm64; + cpuArchitecture = CpuArch.arm64; } _logger.printWarning( 'Unknown architecture $architecture, defaulting to ' @@ -746,7 +746,7 @@ class XCDevice { ); } } - return cpuArchitecture ?? DarwinArch.arm64; + return cpuArchitecture ?? CpuArch.arm64; } /// Error message parsed from xcdevice. null if no error. diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart index 338e160963452..420325ae8e98b 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_aar_test.dart @@ -224,10 +224,10 @@ void main() { expect(buildInfo.flavor, isNull); expect(buildInfo.splitDebugInfoPath, isNull); expect(buildInfo.dartObfuscation, isFalse); - expect(androidBuildInfo.targetArchs, [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - AndroidArch.x86_64, + expect(androidBuildInfo.targetArchs, [ + CpuArch.armv7, + CpuArch.arm64, + CpuArch.x64, ]); } expect(buildModes, hasLength(3)); @@ -272,7 +272,7 @@ void main() { final AndroidBuildInfo androidBuildInfo = (buildAarCall.namedArguments[#androidBuildInfo] as Set).single; - expect(androidBuildInfo.targetArchs, [AndroidArch.x86_64]); + expect(androidBuildInfo.targetArchs, [CpuArch.x64]); final BuildInfo buildInfo = androidBuildInfo.buildInfo; expect(buildInfo.mode, BuildMode.release); diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index 4c67b3e87e943..460cd8e772b98 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -54,7 +54,7 @@ void main() { TargetPlatform.android_x64, ]) { testWithoutContext('AndroidDevice.startApp allows release builds on $targetPlatform', () async { - final String arch = getAndroidArchForName(targetPlatform.getName()).archName; + final String arch = getCpuArchForName(targetPlatform.getName()).androidArchName; final device = AndroidDevice( '1234', modelID: 'TestModel', diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index e6d333afc6e03..af265f170e8d7 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -1092,7 +1092,7 @@ void main() { codeSizeDirectory: 'foo', packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [AndroidArch.arm64_v8a], + targetArchs: [CpuArch.arm64], ), target: 'lib/main.dart', isBuildingBundle: false, @@ -1328,10 +1328,10 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - AndroidArch.arm64_v8a, - AndroidArch.armeabi_v7a, - AndroidArch.x86_64, + targetArchs: [ + CpuArch.arm64, + CpuArch.armv7, + CpuArch.x64, ], ), target: 'lib/main.dart', @@ -1403,10 +1403,10 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - AndroidArch.arm64_v8a, - AndroidArch.armeabi_v7a, - AndroidArch.x86_64, + targetArchs: [ + CpuArch.arm64, + CpuArch.armv7, + CpuArch.x64, ], ), target: 'lib/main.dart', @@ -1471,10 +1471,10 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - AndroidArch.arm64_v8a, - AndroidArch.armeabi_v7a, - AndroidArch.x86_64, + targetArchs: [ + CpuArch.arm64, + CpuArch.armv7, + CpuArch.x64, ], ), target: 'lib/main.dart', @@ -1561,10 +1561,10 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - AndroidArch.arm64_v8a, - AndroidArch.armeabi_v7a, - AndroidArch.x86_64, + targetArchs: [ + CpuArch.arm64, + CpuArch.armv7, + CpuArch.x64, ], ), target: 'lib/main.dart', @@ -1654,10 +1654,10 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - AndroidArch.arm64_v8a, - AndroidArch.armeabi_v7a, - AndroidArch.x86_64, + targetArchs: [ + CpuArch.arm64, + CpuArch.armv7, + CpuArch.x64, ], ), target: 'lib/main.dart', diff --git a/packages/flutter_tools/test/general.shard/android/build_validation_test.dart b/packages/flutter_tools/test/general.shard/android/build_validation_test.dart index 13eacdbd9acff..8103f9e14b4db 100644 --- a/packages/flutter_tools/test/general.shard/android/build_validation_test.dart +++ b/packages/flutter_tools/test/general.shard/android/build_validation_test.dart @@ -13,10 +13,10 @@ void main() { () => validateBuild( const AndroidBuildInfo( BuildInfo.release, - targetArchs: [ - AndroidArch.x86_64, - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, + targetArchs: [ + CpuArch.x64, + CpuArch.armv7, + CpuArch.arm64, ], ), ), @@ -36,7 +36,7 @@ void main() { buildNumber: 'a', packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [AndroidArch.x86_64], + targetArchs: [CpuArch.x64], ), ), throwsToolExit(message: 'buildNumber: a was not a valid integer value.'), @@ -53,7 +53,7 @@ void main() { buildNumber: '-1', packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [AndroidArch.x86_64], + targetArchs: [CpuArch.x64], ), ), throwsToolExit(message: 'buildNumber: -1 must be a positive integer value.'), @@ -70,7 +70,7 @@ void main() { buildNumber: '2100000001', packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [AndroidArch.x86_64], + targetArchs: [CpuArch.x64], ), ), throwsToolExit( @@ -92,7 +92,7 @@ void main() { buildNumber: '2', packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [AndroidArch.x86_64], + targetArchs: [CpuArch.x64], ), ), returnsNormally, 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 32783a6ca0873..2f88ba4a34670 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -70,7 +70,7 @@ void main() { final int result = await genSnapshot.run( snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), - darwinArch: DarwinArch.arm64, + cpuArch: CpuArch.arm64, additionalArgs: ['--additional_arg'], ); expect(result, 0); @@ -130,7 +130,7 @@ void main() { expect( await snapshotter.build( platform: TargetPlatform.ios, - darwinArch: DarwinArch.arm64, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', buildMode: BuildMode.debug, mainPath: 'main.dill', @@ -224,7 +224,7 @@ void main() { buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, - darwinArch: DarwinArch.arm64, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', splitDebugInfo: 'foo', dartObfuscation: false, @@ -284,7 +284,7 @@ void main() { buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, - darwinArch: DarwinArch.arm64, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: true, ); @@ -342,7 +342,7 @@ void main() { buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, - darwinArch: DarwinArch.arm64, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: false, ); diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart index 7cd111bce0fe7..57fcd7f4dad76 100644 --- a/packages/flutter_tools/test/general.shard/build_info_test.dart +++ b/packages/flutter_tools/test/general.shard/build_info_test.dart @@ -95,21 +95,21 @@ void main() { }); testWithoutContext('getDartNameForDarwinArch returns name used in Dart SDK', () { - expect(DarwinArch.armv7.dartName, 'armv7'); - expect(DarwinArch.arm64.dartName, 'arm64'); - expect(DarwinArch.x86_64.dartName, 'x64'); + expect(CpuArch.armv7.dartName, 'armv7'); + expect(CpuArch.arm64.dartName, 'arm64'); + expect(CpuArch.x64.dartName, 'x64'); }); - testWithoutContext('getNameForDarwinArch returns Apple names', () { - expect(DarwinArch.armv7.name, 'armv7'); - expect(DarwinArch.arm64.name, 'arm64'); - expect(DarwinArch.x86_64.name, 'x86_64'); + testWithoutContext('darwinArchName returns Apple names', () { + expect(CpuArch.armv7.darwinArchName, 'armv7'); + expect(CpuArch.arm64.darwinArchName, 'arm64'); + expect(CpuArch.x64.darwinArchName, 'x86_64'); }); testWithoutContext('getNameForTargetPlatform on Darwin arches', () { - expect(TargetPlatform.ios.getName(darwinArch: DarwinArch.arm64), 'ios-arm64'); - expect(TargetPlatform.ios.getName(darwinArch: DarwinArch.armv7), 'ios-armv7'); - expect(TargetPlatform.ios.getName(darwinArch: DarwinArch.x86_64), 'ios-x86_64'); + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.arm64), 'ios-arm64'); + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.armv7), 'ios-armv7'); + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.x64), 'ios-x86_64'); expect(TargetPlatform.android.getName(), isNot(contains('ios'))); }); @@ -124,7 +124,7 @@ void main() { localEngine: 'ios_debug_unopt', ), ).single, - DarwinArch.arm64, + CpuArch.arm64, ); expect( @@ -135,7 +135,7 @@ void main() { localEngine: 'ios_debug_sim_unopt', ), ).single, - DarwinArch.x86_64, + CpuArch.x64, ); expect( @@ -146,18 +146,18 @@ void main() { localEngine: 'ios_debug_sim_unopt_arm64', ), ).single, - DarwinArch.arm64, + CpuArch.arm64, ); expect( defaultIOSArchsForEnvironment(EnvironmentType.physical, Artifacts.test()).single, - DarwinArch.arm64, + CpuArch.arm64, ); - expect( - defaultIOSArchsForEnvironment(EnvironmentType.simulator, Artifacts.test()), - [DarwinArch.x86_64, DarwinArch.arm64], - ); + expect(defaultIOSArchsForEnvironment(EnvironmentType.simulator, Artifacts.test()), [ + CpuArch.x64, + CpuArch.arm64, + ]); }, overrides: { FileSystem: () => MemoryFileSystem.test(), @@ -175,7 +175,7 @@ void main() { localEngine: 'host_debug_unopt', ), ).single, - DarwinArch.x86_64, + CpuArch.x64, ); expect( @@ -185,12 +185,12 @@ void main() { localEngine: 'host_debug_unopt_arm64', ), ).single, - DarwinArch.arm64, + CpuArch.arm64, ); - expect(defaultMacOSArchsForEnvironment(Artifacts.test()), [ - DarwinArch.x86_64, - DarwinArch.arm64, + expect(defaultMacOSArchsForEnvironment(Artifacts.test()), [ + CpuArch.x64, + CpuArch.arm64, ]); }, overrides: { @@ -199,12 +199,15 @@ void main() { }, ); - testWithoutContext('getIOSArchForName on Darwin arches', () { - expect(getIOSArchForName('armv7'), DarwinArch.armv7); - expect(getIOSArchForName('arm64'), DarwinArch.arm64); - expect(getIOSArchForName('arm64e'), DarwinArch.arm64); - expect(getIOSArchForName('x86_64'), DarwinArch.x86_64); - expect(() => getIOSArchForName('bogus'), throwsException); + testWithoutContext('getCpuArchForName on Darwin and Android arches', () { + expect(getCpuArchForName('armv7'), CpuArch.armv7); + expect(getCpuArchForName('arm64'), CpuArch.arm64); + expect(getCpuArchForName('arm64e'), CpuArch.arm64); + expect(getCpuArchForName('x86_64'), CpuArch.x64); + expect(getCpuArchForName('android-arm'), CpuArch.armv7); + expect(getCpuArchForName('android-arm64'), CpuArch.arm64); + expect(getCpuArchForName('android-x64'), CpuArch.x64); + expect(() => getCpuArchForName('bogus'), throwsException); }); testWithoutContext('named BuildInfo has correct defaults', () { diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index d721d4d0784e4..2ae98c8f8a4ad 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -88,7 +88,7 @@ void main() { xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -112,7 +112,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.armv7, + cpuArch: .armv7, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -137,7 +137,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '1.0.0', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -161,7 +161,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '13.1.1', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -185,7 +185,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '10', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -209,7 +209,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '0', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -233,7 +233,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: 'bogus', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -259,7 +259,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '13.3.1', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -285,7 +285,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '13.3.1 (20ADBC)', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -311,7 +311,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '16.4.1(a) (20ADBC)', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -337,7 +337,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: '0', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -363,7 +363,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -385,7 +385,7 @@ void main() { coreDeviceLauncher: coreDeviceLauncher, xcodeDebug: xcodeDebug, name: 'iPhone 1', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, sdkVersion: 'bogus', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, @@ -411,7 +411,7 @@ void main() { xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3 17C54', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -437,7 +437,7 @@ void main() { xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -470,7 +470,7 @@ void main() { xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -561,7 +561,7 @@ void main() { xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, @@ -626,7 +626,7 @@ void main() { 'd83d5bc53967baa0ee18626ba87b6254b2ab5418', name: 'Paired iPhone', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, analytics: FakeAnalytics(), @@ -648,7 +648,7 @@ void main() { '00008027-00192736010F802E', name: 'iPad Pro', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, analytics: FakeAnalytics(), @@ -978,7 +978,7 @@ void main() { '00000001-0000000000000000', name: 'iPad', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, analytics: FakeAnalytics(), diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart index 6f92b9396031f..a5a2699c6c29c 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart @@ -8,7 +8,6 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.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/ios/application_package.dart'; @@ -366,7 +365,7 @@ IOSDevice setUpIOSDevice({ logger: logger, fileSystem: fileSystem ?? MemoryFileSystem.test(), sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, platform: platform, iMobileDevice: IMobileDevice( logger: logger, diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart index d42e12c1e1b80..cc3ddc77ef8ad 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart @@ -7,7 +7,6 @@ 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/base/platform.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/ios/core_devices.dart'; @@ -116,7 +115,7 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { platform: platform, name: 'iPhone 1', sdkVersion: '13.3', - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, iProxy: IProxy.test(logger: logger, processManager: processManager), connectionInterface: DeviceConnectionInterface.attached, isConnected: true, diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index 740ccb288396c..925c291684e6b 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart @@ -1465,7 +1465,7 @@ IOSDevice setUpIOSDevice({ IOSCoreDeviceControl? coreDeviceControl, IOSCoreDeviceLauncher? coreDeviceLauncher, FakeXcodeDebug? xcodeDebug, - DarwinArch cpuArchitecture = DarwinArch.arm64, + CpuArch cpuArchitecture = CpuArch.arm64, FakeExactAnalytics? analytics, }) { artifacts ??= Artifacts.test(); @@ -1500,7 +1500,7 @@ IOSDevice setUpIOSDevice({ coreDeviceControl: coreDeviceControl ?? FakeIOSCoreDeviceControl(), coreDeviceLauncher: coreDeviceLauncher ?? FakeIOSCoreDeviceLauncher(), xcodeDebug: xcodeDebug ?? FakeXcodeDebug(), - cpuArchitecture: cpuArchitecture, + cpuArch: cpuArchitecture, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, isPaired: true, diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index f21c61b02593e..e1df4bc55456f 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -1983,7 +1983,7 @@ IOSDevice setUpIOSDevice({ coreDeviceControl: coreDeviceControl ?? FakeIOSCoreDeviceControl(), coreDeviceLauncher: coreDeviceLauncher ?? FakeIOSCoreDeviceLauncher(), xcodeDebug: xcodeDebug ?? FakeXcodeDebug(), - cpuArchitecture: DarwinArch.arm64, + cpuArch: .arm64, connectionInterface: interfaceType, isConnected: true, isPaired: true, diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index c549eecaa9c34..e305e9a7749c7 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -54,7 +54,7 @@ void main() { defines: { kBuildMode: BuildMode.profile.cliName, kTargetPlatform: TargetPlatform.android.getName(), - kAndroidArchs: AndroidArch.arm64_v8a.platformName, + kAndroidArchs: CpuArch.arm64.androidPlatformName, }, inputs: {}, artifacts: artifacts, diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index 48d57c6896a41..8c63fd11af424 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -1157,14 +1157,14 @@ void main() { expect(devices[0].id, '00008027-00192736010F802E'); expect(devices[0].name, 'An iPhone (Space Gray)'); expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.arm64); expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); expect(devices[0].isConnected, true); expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); expect(devices[1].name, 'iPad 1'); expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54'); - expect(devices[1].cpuArchitecture, DarwinArch.armv7); + expect(await devices[1].cpuArch, CpuArch.armv7); expect(devices[1].connectionInterface, DeviceConnectionInterface.attached); expect(devices[1].isConnected, true); @@ -1172,8 +1172,8 @@ void main() { expect(devices[2].name, 'A networked iPad'); expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54'); expect( - devices[2].cpuArchitecture, - DarwinArch.arm64, + await devices[2].cpuArch, + CpuArch.arm64, ); // Defaults to arm64 for unknown architecture. expect(devices[2].connectionInterface, DeviceConnectionInterface.wireless); expect(devices[2].isConnected, true); @@ -1182,8 +1182,8 @@ void main() { expect(devices[3].name, 'iPad 2'); expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54'); expect( - devices[3].cpuArchitecture, - DarwinArch.arm64, + await devices[3].cpuArch, + CpuArch.arm64, ); // Defaults to arm64 for unknown architecture. expect(devices[3].connectionInterface, DeviceConnectionInterface.attached); expect(devices[3].isConnected, true); @@ -1191,7 +1191,7 @@ void main() { expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); expect(devices[4].name, 'iPhone'); expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[4].cpuArchitecture, DarwinArch.arm64); + expect(await devices[4].cpuArch, CpuArch.arm64); expect(devices[4].connectionInterface, DeviceConnectionInterface.attached); expect(devices[4].isConnected, false); @@ -1308,8 +1308,8 @@ void main() { ), ); final List devices = await xcdevice.getAvailableIOSDevices(); - expect(devices[0].cpuArchitecture, DarwinArch.armv7); - expect(devices[1].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.armv7); + expect(await devices[1].cpuArch, CpuArch.arm64); expect(fakeProcessManager, hasNoRemainingExpectations); }, overrides: { @@ -1422,7 +1422,7 @@ void main() { expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); expect(devices[0].name, 'iPhone'); expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.arm64); expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); expect(devices[0].isConnected, true); @@ -1491,7 +1491,7 @@ void main() { expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); expect(devices[0].name, 'iPhone_2'); expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.arm64); expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); expect(devices[0].isConnected, false); @@ -1561,7 +1561,7 @@ void main() { expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); expect(devices[0].name, 'iPhone_1'); expect(await devices[0].sdkNameAndVersion, 'iOS 14.3 17C54'); - expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.arm64); expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); expect(devices[0].isConnected, false); @@ -1778,7 +1778,7 @@ void main() { expect(devices[0].id, '00008027-00192736010F802E'); expect(devices[0].name, 'An iPhone (Space Gray)'); expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(await devices[0].cpuArch, CpuArch.arm64); expect(devices[0].connectionInterface, DeviceConnectionInterface.wireless); expect(devices[0].isConnected, true); expect(devices[0].devModeEnabled, true); @@ -1786,7 +1786,7 @@ void main() { expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); expect(devices[1].name, 'iPad 1'); expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54'); - expect(devices[1].cpuArchitecture, DarwinArch.armv7); + expect(await devices[1].cpuArch, CpuArch.armv7); expect(devices[1].connectionInterface, DeviceConnectionInterface.attached); expect(devices[1].isConnected, true); expect(devices[1].devModeEnabled, true); @@ -1795,8 +1795,8 @@ void main() { expect(devices[2].name, 'A networked iPad'); expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54'); expect( - devices[2].cpuArchitecture, - DarwinArch.arm64, + await devices[2].cpuArch, + CpuArch.arm64, ); // Defaults to arm64 for unknown architecture. expect(devices[2].connectionInterface, DeviceConnectionInterface.attached); expect(devices[2].isConnected, true); @@ -1806,8 +1806,8 @@ void main() { expect(devices[3].name, 'iPad 2'); expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54'); expect( - devices[3].cpuArchitecture, - DarwinArch.arm64, + await devices[3].cpuArch, + CpuArch.arm64, ); // Defaults to arm64 for unknown architecture. expect(devices[3].connectionInterface, DeviceConnectionInterface.attached); expect(devices[3].isConnected, true); @@ -1816,7 +1816,7 @@ void main() { expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); expect(devices[4].name, 'iPhone'); expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54'); - expect(devices[4].cpuArchitecture, DarwinArch.arm64); + expect(await devices[4].cpuArch, CpuArch.arm64); expect(devices[4].connectionInterface, DeviceConnectionInterface.attached); expect(devices[4].isConnected, false); expect(devices[4].devModeEnabled, true); From 0ed149e63db1f68d2042063baf0a99d969ccd84b Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 23 Jul 2026 20:34:05 -0400 Subject: [PATCH 034/147] Roll Fuchsia Test Scripts from wLST_A-xfOeGT_5mj... to E8hJ1AfK8CtGtaES0... (#189956) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-test-scripts-flutter Please CC chrome-fuchsia-engprod@google.com,jsimmons@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index e19ed03b361c0..e4a6af41f3bc9 100644 --- a/DEPS +++ b/DEPS @@ -201,7 +201,7 @@ vars = { # The version / instance id of the cipd:chromium/fuchsia/test-scripts which # will be used altogether with fuchsia-sdk to setup the build / test # environment. - 'fuchsia_test_scripts_version': 'wLST_A-xfOeGT_5mje7Wi3mmnxOddKpEWQ9PDbQl6QEC', + 'fuchsia_test_scripts_version': 'E8hJ1AfK8CtGtaES0CR9ne88d3fHsWBF9Bv7Icu38k4C', # The version / instance id of the cipd:chromium/fuchsia/gn-sdk which will be # used altogether with fuchsia-sdk to generate gn based build rules. From b65f4d9bc3fed2dc2fabdce3122ebf50f503e848 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 24 Jul 2026 15:10:44 +0000 Subject: [PATCH 035/147] Add dart_runtime_service_vm_aot.dart.snapshot to the snapshot list in the macOS code signing configuration (#189981) This file was added by a recent Dart SDK roll (see https://github.com/flutter/flutter/pull/189949) --- dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart | 1 + engine/src/flutter/build/archives/BUILD.gn | 1 + 2 files changed, 2 insertions(+) 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 6ef24bae1a525..1621dd56c7b55 100644 --- a/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart +++ b/dev/bots/suite_runners/run_verify_binaries_codesigned_tests.dart @@ -121,6 +121,7 @@ List binariesWithoutEntitlements(String flutterRoot) { 'dart-sdk/bin/snapshots/dart2bytecode.dart.snapshot', 'dart-sdk/bin/snapshots/dart2js_aot.dart.snapshot', 'dart-sdk/bin/snapshots/dart2wasm_product.snapshot', + 'dart-sdk/bin/snapshots/dart_runtime_service_vm_aot.dart.snapshot', 'dart-sdk/bin/snapshots/dart_tooling_daemon_aot.dart.snapshot', 'dart-sdk/bin/snapshots/dartdev_aot.dart.snapshot', 'dart-sdk/bin/snapshots/dartdevc_aot.dart.snapshot', diff --git a/engine/src/flutter/build/archives/BUILD.gn b/engine/src/flutter/build/archives/BUILD.gn index 8b4b8f01ab767..9c063a3b00a27 100644 --- a/engine/src/flutter/build/archives/BUILD.gn +++ b/engine/src/flutter/build/archives/BUILD.gn @@ -219,6 +219,7 @@ _dart_sdk_without_entitlement_contents = [ "dart-sdk/bin/snapshots/dart2bytecode.dart.snapshot", "dart-sdk/bin/snapshots/dart2js_aot.dart.snapshot", "dart-sdk/bin/snapshots/dart2wasm_product.snapshot", + "dart-sdk/bin/snapshots/dart_runtime_service_vm_aot.dart.snapshot", "dart-sdk/bin/snapshots/dart_tooling_daemon_aot.dart.snapshot", "dart-sdk/bin/snapshots/dartdev_aot.dart.snapshot", "dart-sdk/bin/snapshots/dartdevc_aot.dart.snapshot", From b4f79899379349bd12b9bc3ca0c70ed9bea18e65 Mon Sep 17 00:00:00 2001 From: zhongliugo Date: Fri, 24 Jul 2026 09:27:18 -0700 Subject: [PATCH 036/147] [web] Cancel touch pointers WebKit abandons mid-gesture (#189608) Fixes #188781 **Problem** On iOS 27, WebKit stops dispatching pointer events for a touch it promotes to a native gesture, such as dragging the caret in a text field, and delivers neither `pointerup` nor `pointercancel`. The framework is left holding a pointer that never lifts, which wedges `BaseTapAndDragGestureRecognizer` so every later tap is silently dropped: the field never regains focus and the keyboard never returns. Affects every iOS 27 browser, since they all run WebKit; iOS 26 is unaffected. **Fix** `_PointerAdapter` tracks which touch devices are down and, on `touchend` and `touchcancel`, cancels any the browser no longer lists in `touches`. Gated to iOSWebKit. **Demo** Before: https://flutter-demo-47-before.web.app After: https://flutter-demo-47-after.web.app **Repro steps:** type in the field, long-press the caret and drag it, lift, then tap the field. Before, the field is permanently dead after a few tries. After, it recovers. --- .../lib/web_ui/lib/src/engine/dom.dart | 13 + .../lib/src/engine/pointer_binding.dart | 97 ++++- .../test/engine/pointer_binding_test.dart | 341 ++++++++++++++++++ 3 files changed, 450 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart index ac296f15b6433..489581eaaea57 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart @@ -1997,6 +1997,19 @@ extension type DomTouchEvent._(JSObject _) implements DomUIEvent { @JS('changedTouches') external _DomList get _changedTouches; Iterable get changedTouches => _createDomListWrapper(_changedTouches); + + @JS('touches') + external _DomList get _touches; + + /// All touch points currently in contact with the surface. + /// + /// On iOS WebKit this stays accurate even where the pointer events do not: + /// WebKit can stop dispatching pointer events for a touch it has taken over + /// for a native gesture, but it still drops that touch from this list once + /// the finger leaves, which is what makes an abandoned touch detectable. This + /// is observed WebKit behavior, not a cross-browser guarantee. + /// See: https://github.com/flutter/flutter/issues/188781 + Iterable get touches => _createDomListWrapper(_touches); } @JS('Touch') diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart index d0b8e14d2a29e..411338fdb11cf 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -482,6 +482,20 @@ class ClickDebouncer { EnginePlatformDispatcher.instance.invokeOnPointerDataPacket(packet); } + /// Flushes any in-progress debounce, then forwards [data] to the framework. + /// + /// For synthetic events that must not be queued or dropped by an in-progress + /// debounce, such as cancels that repair a pointer the browser abandoned. + /// Flushing first keeps the stream ordered: a queued `pointerdown` has to + /// reach the framework before the cancel that closes it out, otherwise the + /// framework is left holding a down it can never match. + void flushAndSend(List data) { + if (isDebouncing) { + _flush(); + } + _sendToFramework(null, data); + } + /// Cancels any pending debounce process and forgets anything that happened so /// far. /// @@ -1007,6 +1021,13 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { final Map _sanitizers = {}; + /// Touch devices that went down and have not been released yet. + /// + /// A device leaves this set when the browser reports `pointerup` or + /// `pointercancel`, or when [_cancelAbandonedTouches] gives up on it. It is + /// what that method reconciles against the touches actually on the surface. + final Set _downTouchDevices = {}; + @visibleForTesting Iterable debugTrackedDevices() => _sanitizers.keys; @@ -1025,7 +1046,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { void _removePointerIfUnhoverable(DomPointerEvent event) { if (event.pointerType == 'touch') { - _sanitizers.remove(event.pointerId); + final int device = _getPointerId(event); + _sanitizers.remove(device); + _downTouchDevices.remove(device); } } @@ -1071,6 +1094,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { buttons: event.buttons!.toInt(), ); _convertEventsToPointerData(data: pointerData, event: event, details: down); + if (event.pointerType == 'touch') { + _downTouchDevices.add(device); + } _callback(event, pointerData); if (event.target == _viewTarget) { @@ -1177,11 +1203,80 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } }, checkModifiers: false); + // Safety net for touches the browser abandons without a `pointerup` or a + // `pointercancel`. See [_cancelAbandonedTouches]. + addEventListener(_globalTarget, 'touchend', (DomEvent event) { + _cancelAbandonedTouches(event as DomTouchEvent); + }); + addEventListener(_globalTarget, 'touchcancel', (DomEvent event) { + _cancelAbandonedTouches(event as DomTouchEvent); + }); + _addWheelEventListener((DomEvent event) { _handleWheelEvent(event); }); } + /// Cancels touch pointers that the browser stopped reporting mid-gesture. + /// + /// iOS WebKit stops dispatching pointer events for a touch once it promotes + /// that touch to a native gesture, such as dragging the caret inside a text + /// field. It delivers neither `pointerup` nor `pointercancel`, so the + /// framework is left with a pointer that never lifts, and any gesture + /// recognizer tracking it is wedged forever. + /// + /// Observed on iOS 27; iOS 26 does not do it. Gated to iOS because the + /// reconciliation below relies on WebKit behavior that other engines do not + /// guarantee, in particular that a `Touch.identifier` equals its pointer + /// event's `pointerId`. Despite its name, [isIosSafari] means WebKit on iOS, + /// so the gate covers every browser on iOS, not just Safari. + /// See: https://github.com/flutter/flutter/issues/188781 + /// + /// On iOS WebKit, `touches` still reports the truth throughout: it lists the + /// touches in contact with the surface, and drops an abandoned one once the + /// finger leaves. Only the pointer events go missing. So any touch this class + /// believes is down, but which the browser does not report as being on the + /// surface, has been abandoned and must be cancelled. + /// + /// The cancel does not join the click debouncer's queue: it repairs an older + /// pointer and must not be dropped by debouncing of a concurrent tap. Any + /// queued events are flushed ahead of it, so the framework still sees the + /// abandoned pointer's `down` before its `cancel`. + void _cancelAbandonedTouches(DomTouchEvent event) { + if (!isIosSafari || _downTouchDevices.isEmpty) { + return; + } + + final onSurface = { + for (final DomTouch touch in event.touches) + if (touch.identifier?.toInt() case final int device) device, + }; + final Set stale = _downTouchDevices.difference(onSurface); + if (stale.isEmpty) { + return; + } + + final pointerData = []; + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); + for (final device in stale) { + _sanitizers.remove(device); + // `convert` defaults `change` to `PointerChange.cancel`. It also replaces + // a cancel's coordinates with the pointer's last known location, so no + // position is supplied here. + _pointerDataConverter.convert( + pointerData, + viewId: _view.viewId, + timeStamp: timeStamp, + signalKind: ui.PointerSignalKind.none, + device: device, + pressureMax: 1.0, + ); + } + _downTouchDevices.removeAll(stale); + + PointerBinding.clickDebouncer.flushAndSend(pointerData); + } + // For each event that is de-coalesced from `event` and described in // `details`, convert it to pointer data and store in `data`. void _convertEventsToPointerData({ diff --git a/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart b/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart index 528c8ee35354a..2a387b23672fd 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart @@ -20,6 +20,42 @@ List _allPointerData(List packets) { return packets.expand((ui.PointerDataPacket packet) => packet.data).toList(); } +/// Whether this browser exposes the `Touch` and `TouchEvent` constructors that +/// [_createTouchEvent] needs. +/// +/// Desktop Safari and Firefox only define them on devices with a touchscreen, +/// so the touch tests below cannot build their input there. +bool get _canConstructTouchEvents => + domWindow.hasProperty('Touch'.toJS).toDart && domWindow.hasProperty('TouchEvent'.toJS).toDart; + +/// Builds a `touchend` or `touchcancel` reporting that the touches in [lifted] +/// left the surface, while those in [remaining] are still on it. +/// +/// A touch identifier is the same value as its pointer event's `pointerId`, so +/// both lists hold device ids. The touch points carry no meaningful coordinates, +/// because the engine only reads their identifiers. +DomTouchEvent _createTouchEvent( + String type, + List lifted, { + List remaining = const [], +}) { + JSAny touch(int device) => DomTouch( + JSObject() + ..setProperty('identifier'.toJS, device.toJS) + ..setProperty('target'.toJS, rootElement as JSAny) + ..setProperty('clientX'.toJS, 0.toJS) + ..setProperty('clientY'.toJS, 0.toJS), + ); + return DomTouchEvent( + type, + JSObject() + ..setProperty('bubbles'.toJS, true.toJS) + ..setProperty('cancelable'.toJS, true.toJS) + ..setProperty('touches'.toJS, [for (final int d in remaining) touch(d)].toJS) + ..setProperty('changedTouches'.toJS, [for (final int d in lifted) touch(d)].toJS), + ); +} + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -2539,6 +2575,311 @@ void testMain() { packets.clear(); }); + // WebKit stops dispatching pointer events for a touch once it promotes that + // touch to a native gesture, such as dragging the caret in a text field. It + // sends neither `pointerup` nor `pointercancel`, which used to leave the + // pointer down forever and wedge any gesture recognizer tracking it. + // Regression test for https://github.com/flutter/flutter/issues/188781 + test('cancels a touch the browser abandons without pointerup', () { + final context = _PointerEventContext(); + // This workaround is gated to iOS WebKit. + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // The finger lifts, but the browser only reports `touchend`. No `pointerup` + // and no `pointercancel` ever arrive. + // + // `touches` is not empty here: it can run ahead of the event stream, and + // already lists touch 3, whose `pointerdown` has not been dispatched yet. + // Touch 2 is absent from it though, which is what marks it as abandoned. + rootElement.dispatchEvent(_createTouchEvent('touchend', [2], remaining: [3])); + + // A cancelled touch is also removed, so exactly two events are emitted. + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[0].buttons, equals(0)); + // The cancel is reported at the pointer's last known location. + expect(packets[0].data[0].physicalX, equals(100 * dpi)); + expect(packets[0].data[0].physicalY, equals(101 * dpi)); + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(2)); + }, skip: !_canConstructTouchEvents); + + test('does not cancel a touch that was released normally', () { + final context = _PointerEventContext(); + // This workaround is gated to iOS WebKit. + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + context + .multiTouchUp(const <_TouchDetails>[_TouchDetails(pointer: 2, clientX: 100, clientY: 101)]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // `pointerup` precedes `touchend` in a healthy sequence, so by now the + // pointer is already released and the trailing `touchend` must be a no-op. + rootElement.dispatchEvent(_createTouchEvent('touchend', [2])); + + expect(packets, isEmpty); + }, skip: !_canConstructTouchEvents); + + // WebKit sometimes drops the `touchend` for an abandoned touch entirely, so + // no event ever announces it. It is still caught because `touches` stops + // listing it, so reconciling against a later touch finds it missing. + test('cancels an abandoned touch whose touchend never arrives', () { + final context = _PointerEventContext(); + // This workaround is gated to iOS WebKit. + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Pointer 2 is abandoned: no pointerup, and no touchend naming it either. + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + + // A later, unrelated touch completes normally. + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 200, clientY: 201), + ]) + .forEach(rootElement.dispatchEvent); + context + .multiTouchUp(const <_TouchDetails>[_TouchDetails(pointer: 3, clientX: 200, clientY: 201)]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // This is pointer 3's touchend, and `touches` is now empty. Pointer 2 is + // absent from it, so it is stale and must be cancelled. + rootElement.dispatchEvent(_createTouchEvent('touchend', [3])); + + expect(packets, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); + expect(packets[0].data[0].device, equals(2)); + }, skip: !_canConstructTouchEvents); + + // The abandoned touch is neither named by this event nor the last finger on + // the surface, so it is only detectable by being absent from `touches`. + test('cancels an abandoned touch while other fingers are still down', () { + final context = _PointerEventContext(); + // This workaround is gated to iOS WebKit. + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Touch 2 is abandoned: no pointerup, and no touchend naming it either. + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + + // Two more fingers go down, and one of them lifts normally. + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 3, clientX: 200, clientY: 201), + _TouchDetails(pointer: 4, clientX: 300, clientY: 301), + ]) + .forEach(rootElement.dispatchEvent); + context + .multiTouchUp(const <_TouchDetails>[_TouchDetails(pointer: 3, clientX: 200, clientY: 201)]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // Pointer 4 is still on the surface, so this is not the last finger up. + // Pointer 2 must still be cancelled, and pointer 4 must be left alone. + rootElement.dispatchEvent(_createTouchEvent('touchend', [3], remaining: [4])); + + // A cancelled touch is also removed, so two events are emitted, both for + // pointer 2. Nothing at all is emitted for pointer 4. + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); + expect(packets[0].data[0].device, equals(2)); + expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); + expect(packets[0].data[1].device, equals(2)); + expect( + packets[0].data.every((ui.PointerData data) => data.device != 4), + isTrue, + reason: 'pointer 4 is still down and must not be cancelled', + ); + }, skip: !_canConstructTouchEvents); + + // An aborted touch is normally cleaned up by the `pointercancel` that + // accompanies `touchcancel`. This covers the case where the browser drops that + // `pointercancel`, which is the same class of defect as the missing + // `pointerup` this whole safety net exists for. + test('cancels an abandoned touch on touchcancel', () { + final context = _PointerEventContext(); + // This workaround is gated to iOS WebKit. + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // The touch is aborted, and no `pointercancel` arrives to release it. + rootElement.dispatchEvent(_createTouchEvent('touchcancel', [2])); + + expect(packets, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); + expect(packets[0].data[0].device, equals(2)); + }, skip: !_canConstructTouchEvents); + + // The reconciliation is gated to iOS WebKit, because it relies on WebKit + // behavior other engines do not guarantee (notably that `Touch.identifier` + // equals `pointerId`). Off iOS it must never cancel a live pointer. + test('does not cancel abandoned touches on non-iOS browsers', () { + // Deliberately does NOT emulate iOS. + final context = _PointerEventContext(); + final packets = []; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + packets.clear(); + + // The same abandonment that would be cancelled on iOS. + rootElement.dispatchEvent(_createTouchEvent('touchend', [2])); + + expect(packets, isEmpty); + }, skip: !_canConstructTouchEvents); + + // A semantics-enabled text field is itself a tappable element, so the touch + // WebKit abandons is usually the very one being debounced: its `down` is still + // queued when the repair runs. Sending the cancel straight through would hand + // the framework a cancel for a pointer it has not seen go down, and the `down` + // would arrive afterwards with nothing left to close it out. That is the same + // stuck state this workaround exists to repair, so the queue has to be flushed + // ahead of the cancel. + test('flushes the debounce queue before delivering the cancel', () { + debugEmulateIosSafari = true; + EngineSemantics.instance.semanticsEnabled = true; + addTearDown(() { + debugEmulateIosSafari = false; + EngineSemantics.instance.semanticsEnabled = false; + }); + + final context = _PointerEventContext(); + final events = <(ui.PointerChange, int)>[]; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + for (final ui.PointerData datum in packet.data) { + events.add((datum.change, datum.device)); + } + }; + + // The touch lands on a tappable element, so it starts click debouncing and + // its `down` sits in the queue instead of going to the framework. + final DomElement tappable = createDomElement('flutter-tappable') + ..setAttribute('flt-tappable', ''); + rootElement.append(tappable); + addTearDown(() { + tappable.remove(); + }); + tappable.dispatchEvent( + context.multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]).single, + ); + expect(PointerBinding.clickDebouncer.isDebouncing, isTrue); + expect(events, isEmpty, reason: 'the debounced down must still be queued'); + + // WebKit abandons that same touch: no pointerup or pointercancel arrives, + // and the finger is gone from the surface by the time touchend fires. + rootElement.dispatchEvent(_createTouchEvent('touchend', [2])); + + expect(events, <(ui.PointerChange, int)>[ + (ui.PointerChange.add, 2), + (ui.PointerChange.down, 2), + (ui.PointerChange.cancel, 2), + (ui.PointerChange.remove, 2), + ], reason: 'the queued down must be flushed ahead of the cancel that closes it out'); + }, skip: !_canConstructTouchEvents); + + // The abandoned pointer's `down` must already have reached the framework + // before its synthesized `cancel`, otherwise the framework sees cancel first. + test('sends the abandoned pointer down before its cancel', () { + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + + final context = _PointerEventContext(); + final events = <(ui.PointerChange, int)>[]; + ui.PlatformDispatcher.instance.onPointerDataPacket = (ui.PointerDataPacket packet) { + for (final ui.PointerData d in packet.data) { + events.add((d.change, d.device)); + } + }; + + // Not cleared: we want the whole ordered stream, down then cancel. + context + .multiTouchDown(const <_TouchDetails>[ + _TouchDetails(pointer: 2, clientX: 100, clientY: 101), + ]) + .forEach(rootElement.dispatchEvent); + rootElement.dispatchEvent(_createTouchEvent('touchend', [2])); + + final int downIndex = events.indexOf((ui.PointerChange.down, 2)); + final int cancelIndex = events.indexOf((ui.PointerChange.cancel, 2)); + expect(downIndex, greaterThanOrEqualTo(0)); + expect(cancelIndex, greaterThanOrEqualTo(0)); + expect(downIndex, lessThan(cancelIndex)); + }, skip: !_canConstructTouchEvents); + test('does not synthesize pointer up if from different device', () { final context = _PointerEventContext(); final packets = []; From 994902bc91adae20a3aafb704085d682d6448386 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 12:27:18 -0400 Subject: [PATCH 037/147] Roll Skia from 6e9c4687c001 to f2f9b52fe1ee (3 revisions) (#189959) https://skia.googlesource.com/skia.git/+log/6e9c4687c001..f2f9b52fe1ee 2026-07-23 michaelludwig@google.com Trace detailed ResourceCache summaries for Ganesh and Graphite 2026-07-23 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-23 sergiog@microsoft.com [rust bmp] Cleanup experimental rust_bmp If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index e4a6af41f3bc9..3a2f45b6e428f 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '6e9c4687c00180d5057b033ee6f099558a2842d6', + 'skia_revision': 'f2f9b52fe1eea06f72e0e47922da80cd66973aa7', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 5d4979dca32beaed486bf2a04b0806e94fccc5da Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 12:29:15 -0400 Subject: [PATCH 038/147] Roll Fuchsia Linux SDK from 9org0yL3yZkp80x5S... to Cdb1RGKCqpcz9z4cl... (#189975) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jsimmons@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 3a2f45b6e428f..cde7e2fb01882 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': '9org0yL3yZkp80x5SQQhN-0gAbPYRnhYOzWU3luJdE4C' + 'version': 'Cdb1RGKCqpcz9z4clTA81sbqT1NH4GfXtp_SKiYYnQsC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From 3a5c2cefdfc73bd2e4e47d8ef80489c060436b37 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 12:35:14 -0400 Subject: [PATCH 039/147] Roll Packages from 8260a1e5670c to 69693296b8df (21 revisions) (#189983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/flutter/packages/compare/8260a1e5670c...69693296b8df 2026-07-24 ionutzubascu@yahoo.com [packages] Migrate all iOS example apps in flutter/packages to UIScene (flutter/packages#12182) 2026-07-23 absarhshah@gmail.com [in_app_purchase] Fixes StoreKit 2 date format does not match in_app_… (flutter/packages#11717) 2026-07-23 269567208+reidbaker-agent@users.noreply.github.com [packages] Add changelog style references to AGENTS.md (flutter/packages#12277) 2026-07-23 ntosarno11@gmail.com [file_selector_android] Surface file-copy failures to Dart instead of crashing (flutter/packages#12107) 2026-07-23 puneetkukreja98@gmail.com [google_maps_flutter] Add support for mapType, fullscreen, and Street View controls on web (flutter/packages#11955) 2026-07-22 engine-flutter-autoroll@skia.org Manual roll Flutter (stable) from ee80f08bbf97 to 84fc5cbb223b (5 revisions) (flutter/packages#12273) 2026-07-22 50643541+Mairramer@users.noreply.github.com [camera_avfoundation] Move camera_avfoundation to Darwin (flutter/packages#11847) 2026-07-22 engine-flutter-autoroll@skia.org Roll Flutter from 1ac2e82fa65c to 2a2a79d36624 (22 revisions) (flutter/packages#12272) 2026-07-22 puneetkukreja98@gmail.com [google_maps_flutter_web]: Adds support for mapTypeControlEnabled, fullscreenControlEnabled, and streetViewControlEnabled. (flutter/packages#12254) 2026-07-21 jmccandless@google.com [material_ui] Prerelease setup (flutter/packages#12258) 2026-07-21 kienhantrung@gmail.com [go_router_builder] Allow users to specify onExit as optional (flutter/packages#11151) 2026-07-21 fluttergithubbot@gmail.com Sync release-cupertino_ui-0.0.2 to main (flutter/packages#12265) 2026-07-21 tarrinneal@gmail.com [pigeon] add support for top level consts (flutter/packages#12032) 2026-07-21 49699333+dependabot[bot]@users.noreply.github.com [dependabot]: Bump androidx.activity:activity from 1.12.4 to 1.13.0 in /packages/image_picker/image_picker_android/android (flutter/packages#11253) 2026-07-21 49699333+dependabot[bot]@users.noreply.github.com [dependabot]: Bump cameraxVersion from 1.6.0 to 1.6.1 in /packages/camera/camera_android_camerax/android (flutter/packages#11862) 2026-07-21 49699333+dependabot[bot]@users.noreply.github.com [dependabot]: Bump com.google.guava:guava from 33.5.0-android to 33.6.0-android in /packages/espresso/android (flutter/packages#11864) 2026-07-21 stuartmorgan@google.com [tool] Rename batch change override label (flutter/packages#12263) 2026-07-21 jmccandless@google.com [cupertino_ui] Bump minor to get to 0.0.2, not patch (flutter/packages#12260) 2026-07-21 stuartmorgan@google.com [tool] Support pre-1.0 versions in batch releases (flutter/packages#12257) 2026-07-21 jmccandless@google.com [material_ui] Remove manual version changes (flutter/packages#12253) 2026-07-21 engine-flutter-autoroll@skia.org Roll Flutter from cab057d904b6 to 1ac2e82fa65c (23 revisions) (flutter/packages#12256) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 4a8596baa32f7..9020a141f5534 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -8260a1e5670ce6a92ee83419975f062e79ca15d0 +69693296b8df2c5068efb45de6c6a963d61163a1 From 285bf671f395d4e4a3e1592e50826cd30fafdd3e Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 24 Jul 2026 17:16:11 +0000 Subject: [PATCH 040/147] Reduce the number of gtest-parallel workers when running Impeller tests on Mac Minis used by CI (#189813) https://github.com/flutter/flutter/pull/188889 started using the Impeller rendering backends in playground tests run by the impeller_unittests harness. Since then, runs of OpenGLESSDF tests on macOS CI have been flaky. These flakes show up as gtest-parallel child processes that fail with exit code -5 (SIGTRAP). The affected CI machines are M1 Mac Minis (hardware model "Macmini9,1") This is an experiment to see if limiting the number of test cases run in parallel on these machines reduces the crashes. See https://github.com/flutter/flutter/issues/189748 --- engine/src/flutter/testing/run_tests.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/testing/run_tests.py b/engine/src/flutter/testing/run_tests.py index 32dd2ba23a047..df89a48384fef 100755 --- a/engine/src/flutter/testing/run_tests.py +++ b/engine/src/flutter/testing/run_tests.py @@ -164,6 +164,12 @@ def is_aarm64() -> bool: return aarm64 +def mac_hardware_model() -> str: + assert is_mac() + output = subprocess.check_output(['sysctl', '-n', 'hw.model']) + return output.decode('utf-8').strip() + + def is_linux() -> bool: return sys_platform.startswith('linux') @@ -568,7 +574,13 @@ def make_test( ) extra_env = metal_validation_env() extra_env.update(vulkan_validation_env(build_dir)) - mac_impeller_unittests_flags = repeat_flags + [ + if mac_hardware_model() == 'Macmini9,1': + # For the Mac Minis used on CI, limit the number of Impeller test cases run in parallel + # in order to reduce the risk of resource exhaustion errors. + workers_flag = ['--workers=%d' % (os.cpu_count() - 2)] + else: + workers_flag = [] + mac_impeller_unittests_flags = repeat_flags + workers_flag + [ '--gtest_filter=-*OpenGLES', # These are covered in the golden tests. '--', '--enable_vulkan_validation', From be25aae20d1b393ef1955628c25c56984b028075 Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:32:06 -0700 Subject: [PATCH 041/147] Remove ancient `tools:tools` dep from `android_sdk` bundle (#189962) This dep has been abandoned https://developer.android.com/tools/releases/sdk-tools Also bumps to include sdk 37, because the underlying script had been bumped without a corresponding bump in the ci.yaml --- .ci.yaml | 232 +++++++++--------- DEPS | 2 +- .../flutter/tools/android_lint/baseline.xml | 27 +- .../flutter/tools/android_sdk/packages.txt | 1 - .../commands.shard/hermetic/daemon_test.dart | 24 +- 5 files changed, 135 insertions(+), 151 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index bcd7286136359..c014e7a41826d 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -53,7 +53,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "android_virtual_device", "version": "android_36_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8719362231152674241"}, {"dependency": "open_jdk", "version": "version:21"}, @@ -76,7 +76,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "android_virtual_device", "version": "android_36_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8702262057250908257"}, {"dependency": "open_jdk", "version": "version:21"}, @@ -100,7 +100,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "android_virtual_device", "version": "android_35_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8733065022087935185"}, {"dependency": "open_jdk", "version": "version:21"}, @@ -117,7 +117,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:8.20.0"} ] @@ -129,7 +129,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:8.20.0"} ] @@ -140,7 +140,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:8.20.0"} ] @@ -151,7 +151,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:8.20.0"} ] @@ -254,7 +254,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -266,7 +266,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] os: Mac-15.7 @@ -344,7 +344,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -377,7 +377,7 @@ targets: test_timeout_secs: "3600" # 1 hour dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:8.20.0"} ] @@ -411,7 +411,7 @@ targets: # Requires Android SDK since we may re-generate Gradle lockfiles dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "gh_cli", "version": "version:2.8.0-2-g32256d38"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -432,7 +432,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -455,7 +455,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -473,7 +473,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -491,7 +491,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -509,7 +509,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -527,7 +527,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -686,7 +686,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -728,7 +728,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -817,7 +817,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -853,7 +853,7 @@ targets: {"dependency": "cmake", "version": "build_id:8787856497187628321"}, {"dependency": "ninja", "version": "version:1.9.0"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] shard: framework_tests subshard: misc @@ -924,7 +924,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -946,7 +946,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -968,7 +968,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -990,7 +990,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1013,7 +1013,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1036,7 +1036,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1059,7 +1059,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1082,7 +1082,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1106,7 +1106,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1130,7 +1130,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1153,7 +1153,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1180,7 +1180,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1320,7 +1320,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -1383,7 +1383,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] tags: > ["devicelab", "hostonly", "linux"] @@ -1396,7 +1396,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1424,7 +1424,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1452,7 +1452,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1480,7 +1480,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1508,7 +1508,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1536,7 +1536,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1564,7 +1564,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1622,7 +1622,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -1648,7 +1648,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:11"} ] task_name: android_java11_dependency_smoke_tests @@ -1671,7 +1671,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1697,7 +1697,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1722,7 +1722,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -1744,7 +1744,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -1780,7 +1780,7 @@ targets: ["framework", "hostonly", "shard", "linux"] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} ] @@ -1805,7 +1805,7 @@ targets: ["framework", "hostonly", "shard", "linux"] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} ] @@ -1832,7 +1832,7 @@ targets: ["framework", "hostonly", "shard", "linux"] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} ] @@ -3935,7 +3935,7 @@ targets: test_timeout_secs: "2700" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -3957,7 +3957,7 @@ targets: test_timeout_secs: "2700" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -3979,7 +3979,7 @@ targets: test_timeout_secs: "2700" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4001,7 +4001,7 @@ targets: test_timeout_secs: "2700" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4023,7 +4023,7 @@ targets: test_timeout_secs: "2700" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4040,7 +4040,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4057,7 +4057,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4074,7 +4074,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4091,7 +4091,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4108,7 +4108,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4322,7 +4322,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] shard: framework_tests subshard: misc @@ -4353,7 +4353,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] shard: framework_tests subshard: misc @@ -4409,7 +4409,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4457,7 +4457,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4477,7 +4477,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4497,7 +4497,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4518,7 +4518,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4600,7 +4600,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] @@ -4665,7 +4665,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4685,7 +4685,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4807,7 +4807,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4833,7 +4833,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4859,7 +4859,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4885,7 +4885,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4911,7 +4911,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4937,7 +4937,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4963,7 +4963,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4989,7 +4989,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -5015,7 +5015,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -5041,7 +5041,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -5069,7 +5069,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests_commands @@ -5084,7 +5084,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests_commands @@ -5099,7 +5099,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -6160,7 +6160,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6177,7 +6177,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6194,7 +6194,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6211,7 +6211,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6228,7 +6228,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6245,7 +6245,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6262,7 +6262,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6279,7 +6279,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6296,7 +6296,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -6384,7 +6384,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] shard: framework_tests subshard: misc @@ -6416,7 +6416,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, {"dependency": "vs_build", "version": "version:vs2019"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v9unmodified"} + {"dependency": "android_sdk", "version": "version:37v2"} ] shard: framework_tests subshard: misc @@ -6502,7 +6502,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6547,7 +6547,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6567,7 +6567,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6587,7 +6587,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6609,7 +6609,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6656,7 +6656,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6676,7 +6676,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6696,7 +6696,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -6858,7 +6858,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -6884,7 +6884,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -6910,7 +6910,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -6936,7 +6936,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -6962,7 +6962,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -6988,7 +6988,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -7014,7 +7014,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -7040,7 +7040,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -7066,7 +7066,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -7092,7 +7092,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "chrome_and_driver", "version": "version:145.0.7632.117"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"}, @@ -7118,7 +7118,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "vs_build", "version": "version:vs2019"} ] @@ -7165,7 +7165,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v9unmodified"}, + {"dependency": "android_sdk", "version": "version:37v2"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests diff --git a/DEPS b/DEPS index cde7e2fb01882..717f6623edcc2 100644 --- a/DEPS +++ b/DEPS @@ -637,7 +637,7 @@ deps = { 'packages': [ { 'package': 'flutter/android/sdk/all/${{platform}}', - 'version': 'version:36v9unmodified' + 'version': 'version:37v2' } ], 'condition': 'download_android_deps', diff --git a/engine/src/flutter/tools/android_lint/baseline.xml b/engine/src/flutter/tools/android_lint/baseline.xml index a374b1cd795a1..74693cb45f5d9 100644 --- a/engine/src/flutter/tools/android_lint/baseline.xml +++ b/engine/src/flutter/tools/android_lint/baseline.xml @@ -1,26 +1,17 @@ - - - + + id="OldTargetApi" + message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details." + errorLine1=" <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="36" />" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="../../../flutter/shell/platform/android/AndroidManifest.xml" + line="8" + column="42"/> diff --git a/engine/src/flutter/tools/android_sdk/packages.txt b/engine/src/flutter/tools/android_sdk/packages.txt index 96dd5a5dba6e6..88fe7f94aa424 100644 --- a/engine/src/flutter/tools/android_sdk/packages.txt +++ b/engine/src/flutter/tools/android_sdk/packages.txt @@ -2,6 +2,5 @@ platforms;android-37.0,platforms;android-36,platforms;android-35,platforms;andro cmdline-tools;latest:cmdline-tools build-tools;37.0.0,build-tools;36.1.0,build-tools;36.0.0,build-tools;35.0.0,build-tools;34.0.0,build-tools;33.0.1:build-tools platform-tools:platform-tools -tools:tools cmake;3.22.1:cmake ndk;28.2.13676358:ndk diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index de97055357570..541d2272e8ab3 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -14,7 +14,6 @@ import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/commands/daemon.dart'; @@ -732,20 +731,15 @@ void main() { expect(response.data['error'], contains('coldBoot is not a bool')); }); - testUsingContext( - 'emulator.getEmulators should respond with list', - () async { - daemon = Daemon(daemonConnection, notifyingLogger: notifyingLogger); - daemonStreams.inputs.add( - DaemonMessage({'id': 0, 'method': 'emulator.getEmulators'}), - ); - final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent); - expect(response.data['id'], 0); - expect(response.data['result'], isList); - }, - // TODO(vashworth): https://github.com/flutter/flutter/issues/189876 - skip: const LocalPlatform().isMacOS, - ); + testUsingContext('emulator.getEmulators should respond with list', () async { + daemon = Daemon(daemonConnection, notifyingLogger: notifyingLogger); + daemonStreams.inputs.add( + DaemonMessage({'id': 0, 'method': 'emulator.getEmulators'}), + ); + final DaemonMessage response = await daemonStreams.outputs.stream.firstWhere(_notEvent); + expect(response.data['id'], 0); + expect(response.data['result'], isList); + }); testUsingContext('daemon can send exposeUrl requests to the client', () async { const originalUrl = 'http://localhost:1234/'; From cb2ddfd7bdddf2c541b691d8cb2649978c2411d9 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Fri, 24 Jul 2026 13:39:02 -0400 Subject: [PATCH 042/147] Rename RegularWindow* to Window* in the windowing API + rename sizedToContent to shrinkWrap in the windowing API (#189363) ## What's new? - Renamed `RegularWindow*` to `Window*` across the windowing API - Rename sizedToContent to shrinkWrap in the windowing API - Left the engine untouched because it is not user-facing ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../windowing_test/lib/main.dart | 10 +- examples/api/lib/widgets/windows/popup.0.dart | 4 +- .../api/lib/widgets/windows/satellite.0.dart | 4 +- .../api/lib/widgets/windows/tooltip.0.dart | 4 +- .../lib/widgets/windows/window_manager.0.dart | 2 +- .../multiple_windows/lib/app/main_window.dart | 34 ++-- examples/multiple_windows/lib/app/models.dart | 12 +- ...indow_content.dart => window_content.dart} | 36 ++-- ...it_dialog.dart => window_edit_dialog.dart} | 18 +- .../lib/app/window_settings_dialog.dart | 28 +-- examples/multiple_windows/lib/main.dart | 4 +- packages/flutter/lib/src/widgets/_window.dart | 162 +++++++++--------- .../lib/src/widgets/_window_linux.dart | 30 ++-- .../lib/src/widgets/_window_macos.dart | 30 ++-- .../lib/src/widgets/_window_win32.dart | 82 ++++----- .../flutter/test/widgets/windowing_test.dart | 115 ++++++------- packages/flutter_test/lib/src/binding.dart | 34 ++-- 17 files changed, 303 insertions(+), 306 deletions(-) rename examples/multiple_windows/lib/app/{regular_window_content.dart => window_content.dart} (78%) rename examples/multiple_windows/lib/app/{regular_window_edit_dialog.dart => window_edit_dialog.dart} (90%) diff --git a/dev/integration_tests/windowing_test/lib/main.dart b/dev/integration_tests/windowing_test/lib/main.dart index 341c45e462c1d..98e43468db478 100644 --- a/dev/integration_tests/windowing_test/lib/main.dart +++ b/dev/integration_tests/windowing_test/lib/main.dart @@ -14,7 +14,7 @@ import 'package:flutter/src/widgets/_window.dart'; import 'package:flutter/src/widgets/_window_positioner.dart'; import 'package:flutter_driver/driver_extension.dart'; -late final RegularWindowController controller; +late final WindowController controller; final ValueNotifier dialogController = ValueNotifier(null); /// A generic "secondary" window used by the `isDestroyed` end-to-end tests. @@ -180,7 +180,7 @@ void main() { try { switch (windowType) { case 'regular': - secondaryController = RegularWindowController( + secondaryController = WindowController( size: const Size(200, 200), title: 'Secondary', ); @@ -228,13 +228,13 @@ void main() { } }, ); - controller = RegularWindowController( + controller = WindowController( size: const Size(640, 480), title: 'Integration Test', - delegate: RegularWindowControllerDelegate(), + delegate: WindowControllerDelegate(), ); - runWidget(RegularWindow(controller: controller, child: const MyApp())); + runWidget(Window(controller: controller, child: const MyApp())); windowCreated.complete(); } diff --git a/examples/api/lib/widgets/windows/popup.0.dart b/examples/api/lib/widgets/windows/popup.0.dart index 60f57c8dad259..ecfdc65e762eb 100644 --- a/examples/api/lib/widgets/windows/popup.0.dart +++ b/examples/api/lib/widgets/windows/popup.0.dart @@ -15,8 +15,8 @@ import 'package:flutter/src/widgets/_window_positioner.dart'; void main() { try { runWidget( - RegularWindow( - controller: RegularWindowController( + Window( + controller: WindowController( size: const Size(800, 600), constraints: const BoxConstraints(minWidth: 640, minHeight: 480), title: 'Example Window', diff --git a/examples/api/lib/widgets/windows/satellite.0.dart b/examples/api/lib/widgets/windows/satellite.0.dart index fab69069a180e..f52ca066d3f79 100644 --- a/examples/api/lib/widgets/windows/satellite.0.dart +++ b/examples/api/lib/widgets/windows/satellite.0.dart @@ -14,8 +14,8 @@ void main() { try { WidgetsFlutterBinding.ensureInitialized(); runWidget( - RegularWindow( - controller: RegularWindowController( + Window( + controller: WindowController( size: const Size(800, 600), constraints: const BoxConstraints(minWidth: 640, minHeight: 480), title: 'Example Window', diff --git a/examples/api/lib/widgets/windows/tooltip.0.dart b/examples/api/lib/widgets/windows/tooltip.0.dart index 72db917d1406d..c15216a0d0370 100644 --- a/examples/api/lib/widgets/windows/tooltip.0.dart +++ b/examples/api/lib/widgets/windows/tooltip.0.dart @@ -15,8 +15,8 @@ import 'package:flutter/src/widgets/_window_positioner.dart'; void main() { try { runWidget( - RegularWindow( - controller: RegularWindowController( + Window( + controller: WindowController( size: const Size(800, 600), constraints: const BoxConstraints(minWidth: 640, minHeight: 480), title: 'Example Window', diff --git a/examples/api/lib/widgets/windows/window_manager.0.dart b/examples/api/lib/widgets/windows/window_manager.0.dart index e8d0d178d7657..634a0fafa680f 100644 --- a/examples/api/lib/widgets/windows/window_manager.0.dart +++ b/examples/api/lib/widgets/windows/window_manager.0.dart @@ -12,7 +12,7 @@ import 'package:flutter/src/widgets/_window.dart'; void main() { try { WidgetsFlutterBinding.ensureInitialized(); - final RegularWindowController controller = RegularWindowController( + final WindowController controller = WindowController( size: const Size(800, 600), ); runWidget( diff --git a/examples/multiple_windows/lib/app/main_window.dart b/examples/multiple_windows/lib/app/main_window.dart index d8be3ed5b2cd1..a45ab8d759d58 100644 --- a/examples/multiple_windows/lib/app/main_window.dart +++ b/examples/multiple_windows/lib/app/main_window.dart @@ -13,16 +13,16 @@ import 'dialog_window_edit_dialog.dart'; import 'models.dart'; import 'popup_button.dart'; import 'popup_window_edit_dialog.dart'; -import 'regular_window_content.dart'; -import 'regular_window_edit_dialog.dart'; import 'tooltip_button.dart'; import 'tooltip_window_edit_dialog.dart'; +import 'window_content.dart'; +import 'window_edit_dialog.dart'; import 'window_settings_dialog.dart'; class MainWindow extends StatelessWidget { const MainWindow({super.key, required this.controller}); - final RegularWindowController controller; + final WindowController controller; @override Widget build(BuildContext context) { @@ -58,7 +58,7 @@ class MainWindow extends StatelessWidget { class _WindowsTable extends StatelessWidget { const _WindowsTable({required this.mainWindow}); - final RegularWindowController mainWindow; + final WindowController mainWindow; DataRow _buildRow(BaseWindowController controller, BuildContext context) { return DataRow( @@ -105,7 +105,7 @@ class _WindowsTable extends StatelessWidget { void _showWindowEditDialog(BaseWindowController controller, BuildContext context) { return switch (controller) { - final RegularWindowController regular => showRegularWindowEditDialog( + final WindowController regular => showWindowEditDialog( context: context, controller: regular, ), @@ -127,7 +127,7 @@ class _WindowsTable extends StatelessWidget { static String _getWindowTypeName(BaseWindowController controller) { return switch (controller) { - RegularWindowController() => 'Regular', + WindowController() => 'Regular', DialogWindowController() => 'Dialog', TooltipWindowController() => 'Tooltip', PopupWindowController() => 'Popup', @@ -187,18 +187,18 @@ class _WindowCreatorCard extends StatelessWidget { OutlinedButton( onPressed: () { late final WindowEntry entry; - final RegularWindowController controller; - if (windowSettings.regularSizedToContent) { - controller = RegularWindowController.sizedToContent( + final WindowController controller; + if (windowSettings.shrinkWrap) { + controller = WindowController.shrinkWrap( resizable: windowSettings.regularResizable, - delegate: CallbackRegularWindowControllerDelegate( + delegate: CallbackWindowControllerDelegate( onDestroyed: () => windowRegistry.unregister(entry), ), title: 'Regular', ); } else { - controller = RegularWindowController( - delegate: CallbackRegularWindowControllerDelegate( + controller = WindowController( + delegate: CallbackWindowControllerDelegate( onDestroyed: () => windowRegistry.unregister(entry), ), title: 'Regular', @@ -209,7 +209,7 @@ class _WindowCreatorCard extends StatelessWidget { entry = WindowEntry( controller: controller, builder: (BuildContext context) => - RegularWindowContent(regularWindowController: controller), + WindowContent(windowController: controller), ); windowRegistry.register(entry); }, @@ -222,8 +222,8 @@ class _WindowCreatorCard extends StatelessWidget { onPressed: () { late final WindowEntry entry; final DialogWindowController controller; - if (windowSettings.dialogSizedToContent) { - controller = DialogWindowController.sizedToContent( + if (windowSettings.dialogShrinkWrap) { + controller = DialogWindowController.shrinkWrap( resizable: windowSettings.dialogResizable, delegate: CallbackDialogWindowControllerDelegate( onDestroyed: () => windowRegistry.unregister(entry), @@ -254,8 +254,8 @@ class _WindowCreatorCard extends StatelessWidget { onPressed: () { late final WindowEntry entry; final DialogWindowController controller; - if (windowSettings.dialogSizedToContent) { - controller = DialogWindowController.sizedToContent( + if (windowSettings.dialogShrinkWrap) { + controller = DialogWindowController.shrinkWrap( resizable: windowSettings.dialogResizable, delegate: CallbackDialogWindowControllerDelegate( onDestroyed: () => windowRegistry.unregister(entry), diff --git a/examples/multiple_windows/lib/app/models.dart b/examples/multiple_windows/lib/app/models.dart index 30d198d69e38a..7e481e778cd11 100644 --- a/examples/multiple_windows/lib/app/models.dart +++ b/examples/multiple_windows/lib/app/models.dart @@ -15,10 +15,10 @@ class TooltipSettings {} class WindowSettings { WindowSettings({ this.regularSize = const Size(800, 600), - this.regularSizedToContent = false, + this.shrinkWrap = false, this.regularResizable = true, this.dialogSize = const Size(400, 400), - this.dialogSizedToContent = false, + this.dialogShrinkWrap = false, this.dialogResizable = true, this.positioner = const WindowPositioner( parentAnchor: WindowPositionerAnchor.right, @@ -27,21 +27,21 @@ class WindowSettings { }); /// The initial size for newly created regular windows. - /// Ignored when [regularSizedToContent] is true. + /// Ignored when [shrinkWrap] is true. Size regularSize; /// If true, new regular windows will be sized to fit their content. - bool regularSizedToContent; + bool shrinkWrap; /// If true, regular windows may be manually resized by the user. bool regularResizable; /// The initial size of the dialog window. - /// Ignored when [dialogSizedToContent] is true. + /// Ignored when [dialogShrinkWrap] is true. Size dialogSize; /// If true, new dialog windows will be sized to fit their content. - bool dialogSizedToContent; + bool dialogShrinkWrap; /// If true, dialog windows may be manually resized by the user. bool dialogResizable; diff --git a/examples/multiple_windows/lib/app/regular_window_content.dart b/examples/multiple_windows/lib/app/window_content.dart similarity index 78% rename from examples/multiple_windows/lib/app/regular_window_content.dart rename to examples/multiple_windows/lib/app/window_content.dart index df9498bc7e872..393c3be452f20 100644 --- a/examples/multiple_windows/lib/app/regular_window_content.dart +++ b/examples/multiple_windows/lib/app/window_content.dart @@ -16,16 +16,16 @@ import 'popup_button.dart'; import 'rotated_wire_cube.dart'; import 'tooltip_button.dart'; -class RegularWindowContent extends StatefulWidget { - const RegularWindowContent({super.key, required this.regularWindowController}); +class WindowContent extends StatefulWidget { + const WindowContent({super.key, required this.windowController}); - final RegularWindowController regularWindowController; + final WindowController windowController; @override - State createState() => _RegularWindowContentState(); + State createState() => _WindowContentState(); } -class _RegularWindowContentState extends State { +class _WindowContentState extends State { late final Color cubeColor; static Color _generateRandomDarkColor() { @@ -70,15 +70,15 @@ class _RegularWindowContentState extends State { mainAxisSize: .min, children: [ _WindowCreationButtons( - regularWindowController: widget.regularWindowController, + windowController: widget.windowController, ), const SizedBox(height: 20), - TooltipButton(parentController: widget.regularWindowController), + TooltipButton(parentController: widget.windowController), const SizedBox(height: 20), - PopupButton(parentController: widget.regularWindowController), + PopupButton(parentController: widget.windowController), const SizedBox(height: 20), Text( - 'View #${widget.regularWindowController.rootView.viewId}\n' + 'View #${widget.windowController.rootView.viewId}\n' 'Size: ${windowSize.width.toStringAsFixed(1)}\u00D7${windowSize.height.toStringAsFixed(1)}\n' 'Device Pixel Ratio: $dpr', textAlign: TextAlign.center, @@ -98,11 +98,11 @@ class _RegularWindowContentState extends State { /// Extracted widget that depends on [WindowRegistry] so that registry changes /// (e.g. opening/closing windows) only rebuild these buttons, not the entire -/// [RegularWindowContent] tree. +/// [WindowContent] tree. class _WindowCreationButtons extends StatelessWidget { - const _WindowCreationButtons({required this.regularWindowController}); + const _WindowCreationButtons({required this.windowController}); - final RegularWindowController regularWindowController; + final WindowController windowController; @override Widget build(BuildContext context) { @@ -115,8 +115,8 @@ class _WindowCreationButtons extends StatelessWidget { ElevatedButton( onPressed: () { late final WindowEntry entry; - final controller = RegularWindowController( - delegate: CallbackRegularWindowControllerDelegate( + final controller = WindowController( + delegate: CallbackWindowControllerDelegate( onDestroyed: () => windowRegistry.unregister(entry), ), title: 'Regular', @@ -126,7 +126,7 @@ class _WindowCreationButtons extends StatelessWidget { entry = WindowEntry( controller: controller, builder: (BuildContext context) => - RegularWindowContent(regularWindowController: controller), + WindowContent(windowController: controller), ); windowRegistry.register(entry); }, @@ -142,7 +142,7 @@ class _WindowCreationButtons extends StatelessWidget { ), title: 'Modal Dialog', size: windowSettings.dialogSize, - parent: regularWindowController, + parent: windowController, ); entry = WindowEntry( @@ -159,8 +159,8 @@ class _WindowCreationButtons extends StatelessWidget { } } -class CallbackRegularWindowControllerDelegate with RegularWindowControllerDelegate { - CallbackRegularWindowControllerDelegate({required this.onDestroyed}); +class CallbackWindowControllerDelegate with WindowControllerDelegate { + CallbackWindowControllerDelegate({required this.onDestroyed}); @override void onWindowDestroyed() { diff --git a/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart b/examples/multiple_windows/lib/app/window_edit_dialog.dart similarity index 90% rename from examples/multiple_windows/lib/app/regular_window_edit_dialog.dart rename to examples/multiple_windows/lib/app/window_edit_dialog.dart index 0232d30637539..0a77718971713 100644 --- a/examples/multiple_windows/lib/app/regular_window_edit_dialog.dart +++ b/examples/multiple_windows/lib/app/window_edit_dialog.dart @@ -8,28 +8,28 @@ import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; -void showRegularWindowEditDialog({ +void showWindowEditDialog({ required BuildContext context, - required RegularWindowController controller, + required WindowController controller, }) { showDialog( context: context, builder: (context) => - _RegularWindowEditDialog(controller: controller, onClose: () => Navigator.pop(context)), + _WindowEditDialog(controller: controller, onClose: () => Navigator.pop(context)), ); } -class _RegularWindowEditDialog extends StatefulWidget { - const _RegularWindowEditDialog({required this.controller, required this.onClose}); +class _WindowEditDialog extends StatefulWidget { + const _WindowEditDialog({required this.controller, required this.onClose}); - final RegularWindowController controller; + final WindowController controller; final VoidCallback onClose; @override - State createState() => _RegularWindowEditDialogState(); + State createState() => _WindowEditDialogState(); } -class _RegularWindowEditDialogState extends State<_RegularWindowEditDialog> { +class _WindowEditDialogState extends State<_WindowEditDialog> { late Size initialSize; late String initialTitle; late bool initialFullscreen; @@ -67,7 +67,7 @@ class _RegularWindowEditDialogState extends State<_RegularWindowEditDialog> { } @override - void didUpdateWidget(covariant _RegularWindowEditDialog oldWidget) { + void didUpdateWidget(covariant _WindowEditDialog oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.controller != widget.controller) { oldWidget.controller.removeListener(_onNotification); diff --git a/examples/multiple_windows/lib/app/window_settings_dialog.dart b/examples/multiple_windows/lib/app/window_settings_dialog.dart index bec23eda95e47..f01e4e05b806a 100644 --- a/examples/multiple_windows/lib/app/window_settings_dialog.dart +++ b/examples/multiple_windows/lib/app/window_settings_dialog.dart @@ -40,9 +40,9 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { final TextEditingController _offsetDxController = TextEditingController(); final TextEditingController _offsetDyController = TextEditingController(); - late bool _regularSizedToContent; + late bool _regularShrinkWrap; late bool _regularResizable; - late bool _dialogSizedToContent; + late bool _dialogShrinkWrap; late bool _dialogResizable; late bool _flipX; @@ -67,9 +67,9 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { _dialogHeightController.addListener(_updateDialogSize); _dialogWidthController.text = widget.settings.dialogSize.width.toString(); _dialogHeightController.text = widget.settings.dialogSize.height.toString(); - _regularSizedToContent = widget.settings.regularSizedToContent; + _regularShrinkWrap = widget.settings.shrinkWrap; _regularResizable = widget.settings.regularResizable; - _dialogSizedToContent = widget.settings.dialogSizedToContent; + _dialogShrinkWrap = widget.settings.dialogShrinkWrap; _dialogResizable = widget.settings.dialogResizable; _offsetDxController.text = widget.settings.positioner.offset.dx.toString(); _offsetDyController.text = widget.settings.positioner.offset.dy.toString(); @@ -127,7 +127,7 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { child: TextFormField( controller: _regularWidthController, decoration: const InputDecoration(labelText: 'Initial width'), - enabled: !_regularSizedToContent, + enabled: !_regularShrinkWrap, ), ), const SizedBox(width: 20), @@ -135,7 +135,7 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { child: TextFormField( controller: _regularHeightController, decoration: const InputDecoration(labelText: 'Initial height'), - enabled: !_regularSizedToContent, + enabled: !_regularShrinkWrap, ), ), ], @@ -145,8 +145,8 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { children: [ const SizedBox(width: 100, child: Text('Sized to content')), Switch( - value: _regularSizedToContent, - onChanged: (bool value) => setState(() => _regularSizedToContent = value), + value: _regularShrinkWrap, + onChanged: (bool value) => setState(() => _regularShrinkWrap = value), ), const SizedBox(width: 24), const SizedBox(width: 70, child: Text('Resizable')), @@ -173,7 +173,7 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { child: TextFormField( controller: _dialogWidthController, decoration: const InputDecoration(labelText: 'Initial width'), - enabled: !_dialogSizedToContent, + enabled: !_dialogShrinkWrap, ), ), const SizedBox(width: 20), @@ -181,7 +181,7 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { child: TextFormField( controller: _dialogHeightController, decoration: const InputDecoration(labelText: 'Initial height'), - enabled: !_dialogSizedToContent, + enabled: !_dialogShrinkWrap, ), ), ], @@ -191,8 +191,8 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { children: [ const SizedBox(width: 100, child: Text('Sized to content')), Switch( - value: _dialogSizedToContent, - onChanged: (bool value) => setState(() => _dialogSizedToContent = value), + value: _dialogShrinkWrap, + onChanged: (bool value) => setState(() => _dialogShrinkWrap = value), ), const SizedBox(width: 24), const SizedBox(width: 70, child: Text('Resizable')), @@ -383,13 +383,13 @@ class _WindowSettingsEditorState extends State<_WindowSettingsEditor> { double.tryParse(_regularHeightController.text) ?? widget.settings.regularSize.height, ); - widget.settings.regularSizedToContent = _regularSizedToContent; + widget.settings.shrinkWrap = _regularShrinkWrap; widget.settings.regularResizable = _regularResizable; widget.settings.dialogSize = Size( double.tryParse(_dialogWidthController.text) ?? widget.settings.dialogSize.width, double.tryParse(_dialogHeightController.text) ?? widget.settings.dialogSize.height, ); - widget.settings.dialogSizedToContent = _dialogSizedToContent; + widget.settings.dialogShrinkWrap = _dialogShrinkWrap; widget.settings.dialogResizable = _dialogResizable; widget.settings.positioner = widget.settings.positioner.copyWith( diff --git a/examples/multiple_windows/lib/main.dart b/examples/multiple_windows/lib/main.dart index 1bebab3e4208f..b35e4a28e0691 100644 --- a/examples/multiple_windows/lib/main.dart +++ b/examples/multiple_windows/lib/main.dart @@ -14,7 +14,7 @@ import 'package:flutter/src/widgets/_window.dart'; import 'app/main_window.dart'; import 'app/models.dart'; -class MainControllerWindowDelegate with RegularWindowControllerDelegate { +class MainControllerWindowDelegate with WindowControllerDelegate { @override void onWindowDestroyed() { super.onWindowDestroyed(); @@ -35,7 +35,7 @@ class MultiWindowApp extends StatefulWidget { } class _MultiWindowAppState extends State { - final RegularWindowController controller = RegularWindowController( + final WindowController controller = WindowController( size: const Size(800, 600), title: 'Multi-Window Reference Application', delegate: MainControllerWindowDelegate(), diff --git a/packages/flutter/lib/src/widgets/_window.dart b/packages/flutter/lib/src/widgets/_window.dart index 9cccaf08f6016..c7a1f8833f784 100644 --- a/packages/flutter/lib/src/widgets/_window.dart +++ b/packages/flutter/lib/src/widgets/_window.dart @@ -61,7 +61,7 @@ See: https://github.com/flutter/flutter/issues/30701. /// /// See also: /// -/// * [RegularWindowController], the controller for regular top-level windows. +/// * [WindowController], the controller for regular top-level windows. @internal sealed class BaseWindowController extends ChangeNotifier { /// The current size of the drawable area of the window. @@ -111,10 +111,10 @@ sealed class BaseWindowController extends ChangeNotifier { /// /// See also: /// -/// * [RegularWindowController], the controller that creates and manages regular windows. -/// * [RegularWindow], the widget for a regular window. +/// * [WindowController], the controller that creates and manages regular windows. +/// * [Window], the widget for a regular window. @internal -mixin class RegularWindowControllerDelegate { +mixin class WindowControllerDelegate { /// Invoked when the user attempts to close the window. /// /// The default implementation destroys the window. Subclasses @@ -126,7 +126,7 @@ mixin class RegularWindowControllerDelegate { /// /// * [onWindowDestroyed], which is invoked after the window is closed. @internal - void onWindowCloseRequested(RegularWindowController controller) { + void onWindowCloseRequested(WindowController controller) { if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); } @@ -156,7 +156,7 @@ mixin class RegularWindowControllerDelegate { /// platform with the provided properties. /// /// This class does not interact with the widget tree. Instead, it is typically -/// provided to the [RegularWindow] widget, who does the work of rendering the +/// provided to the [Window] widget, who does the work of rendering the /// content inside of this window. /// /// The user of this class is responsible for managing the lifecycle of the window. @@ -176,8 +176,8 @@ mixin class RegularWindowControllerDelegate { /// /// void main() { /// runWidget( -/// RegularWindow( -/// controller: RegularWindowController( +/// Window( +/// controller: WindowController( /// size: const Size(800, 600), /// constraints: const BoxConstraints(minWidth: 640, minHeight: 480), /// title: 'Example Window', @@ -189,13 +189,13 @@ mixin class RegularWindowControllerDelegate { /// ``` /// {@end-tool} /// -/// Children of a [RegularWindow] widget can access the [RegularWindowController] +/// Children of a [Window] widget can access the [WindowController] /// via the [WindowScope] inherited widget. /// /// {@macro flutter.widgets.windowing.experimental} @internal -abstract class RegularWindowController extends BaseWindowController { - /// Creates a [RegularWindowController] with a specific size. +abstract class WindowController extends BaseWindowController { + /// Creates a [WindowController] with a specific size. /// /// Upon construction, the window is created by the platform with the /// given [size]. @@ -217,7 +217,7 @@ abstract class RegularWindowController extends BaseWindowController { /// {@endtemplate} /// /// To create a window that is sized to its content instead, use - /// [RegularWindowController.sizedToContent]. + /// [WindowController.shrinkWrap]. /// /// {@template flutter.widgets.windowing.shared} /// The [title] argument configures the window's title. @@ -230,11 +230,11 @@ abstract class RegularWindowController extends BaseWindowController { /// /// {@macro flutter.widgets.windowing.experimental} @internal - factory RegularWindowController({ + factory WindowController({ required Size size, BoxConstraints? constraints, String? title, - RegularWindowControllerDelegate? delegate, + WindowControllerDelegate? delegate, }) { if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); @@ -245,8 +245,8 @@ abstract class RegularWindowController extends BaseWindowController { } final WindowingOwner owner = WidgetsBinding.instance.windowingOwner; - return owner.createRegularWindowController( - delegate: delegate ?? RegularWindowControllerDelegate(), + return owner.createWindowController( + delegate: delegate ?? WindowControllerDelegate(), size: size, constraints: constraints, title: title, @@ -254,9 +254,9 @@ abstract class RegularWindowController extends BaseWindowController { ); } - /// Creates a [RegularWindowController] that sizes the window to its content. + /// Creates a [WindowController] that sizes the window to its content. /// - /// {@template flutter.widgets.windowing.sizedToContentConstructor} + /// {@template flutter.widgets.windowing.shrinkWrapConstructor} /// The window is created by the platform and initially /// sized to fit its content. /// @@ -279,17 +279,17 @@ abstract class RegularWindowController extends BaseWindowController { /// {@endtemplate} /// /// To create a window with a specific size instead, use the default - /// [RegularWindowController] constructor. + /// [WindowController] constructor. /// /// {@macro flutter.widgets.windowing.shared} /// /// {@macro flutter.widgets.windowing.experimental} @internal - factory RegularWindowController.sizedToContent({ + factory WindowController.shrinkWrap({ bool resizable = false, BoxConstraints? constraints, String? title, - RegularWindowControllerDelegate? delegate, + WindowControllerDelegate? delegate, }) { if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); @@ -298,27 +298,27 @@ abstract class RegularWindowController extends BaseWindowController { WidgetsFlutterBinding.ensureInitialized(); final WindowingOwner owner = WidgetsBinding.instance.windowingOwner; - return owner.createRegularWindowController( - delegate: delegate ?? RegularWindowControllerDelegate(), + return owner.createWindowController( + delegate: delegate ?? WindowControllerDelegate(), constraints: constraints, resizable: resizable, title: title, ); } - /// Creates an empty [RegularWindowController]. + /// Creates an empty [WindowController]. /// /// This method is only intended to be used by subclasses of the - /// [RegularWindowController]. + /// [WindowController]. /// - /// Users who want to instantiate a new [RegularWindowController] should + /// Users who want to instantiate a new [WindowController] should /// always use the factory method to create a controller that is valid /// for their particular platform. /// /// {@macro flutter.widgets.windowing.experimental} @internal @protected - RegularWindowController.empty(); + WindowController.empty(); /// The current title of the window. /// @@ -445,7 +445,7 @@ abstract class RegularWindowController extends BaseWindowController { /// /// * [DialogWindowController], the controller that creates and manages dialog windows. /// * [DialogWindow], the widget for a dialog window. -/// * [RegularWindowControllerDelegate], the delegate for regular window controllers. +/// * [WindowControllerDelegate], the delegate for regular window controllers. @internal mixin class DialogWindowControllerDelegate { /// Invoked when the user attempts to close the window. @@ -511,8 +511,8 @@ mixin class DialogWindowControllerDelegate { /// /// void main() { /// runWidget( -/// RegularWindow( -/// controller: RegularWindowController( +/// Window( +/// controller: WindowController( /// size: const Size(800, 600), /// constraints: const BoxConstraints(minWidth: 640, minHeight: 480), /// title: 'Example Window', @@ -555,7 +555,7 @@ abstract class DialogWindowController extends BaseWindowController { /// {@macro flutter.widgets.windowing.sizedConstructor} /// /// To create a dialog that is sized to its content instead, use - /// [DialogWindowController.sizedToContent]. + /// [DialogWindowController.shrinkWrap]. /// /// {@template flutter.widgets.windowing.dialogParent} /// The [parent] argument specifies the parent window of this dialog. @@ -601,7 +601,7 @@ abstract class DialogWindowController extends BaseWindowController { /// Creates a [DialogWindowController] that sizes the window to its content. /// - /// {@macro flutter.widgets.windowing.sizedToContentConstructor} + /// {@macro flutter.widgets.windowing.shrinkWrapConstructor} /// /// To create a dialog with a specific size instead, use the default /// [DialogWindowController] constructor. @@ -611,7 +611,7 @@ abstract class DialogWindowController extends BaseWindowController { /// {@macro flutter.widgets.windowing.shared} /// /// {@macro flutter.widgets.windowing.experimental} - factory DialogWindowController.sizedToContent({ + factory DialogWindowController.shrinkWrap({ bool resizable = false, BoxConstraints? constraints, BaseWindowController? parent, @@ -738,7 +738,7 @@ abstract class DialogWindowController extends BaseWindowController { /// /// * [TooltipWindowController], the controller that creates and manages tooltip windows. /// * [TooltipWindow], the widget for a tooltip window. -/// * [RegularWindowControllerDelegate], the delegate for regular window controllers. +/// * [WindowControllerDelegate], the delegate for regular window controllers. mixin class TooltipWindowControllerDelegate { /// Invoked after the window is closed. /// @@ -883,7 +883,7 @@ abstract class TooltipWindowController extends BaseWindowController { /// /// * [PopupWindowController], the controller that creates and manages popup windows. /// * [PopupWindow], the widget for a popup window. -/// * [RegularWindowControllerDelegate], the delegate for regular window controllers. +/// * [WindowControllerDelegate], the delegate for regular window controllers. mixin class PopupWindowControllerDelegate { /// Invoked after the window is closed. /// @@ -1025,7 +1025,7 @@ abstract class PopupWindowController extends BaseWindowController { void activate() { BaseWindowController parent = this.parent; while (true) { - if (parent is RegularWindowController) { + if (parent is WindowController) { parent.activate(); break; } else if (parent is DialogWindowController) { @@ -1046,7 +1046,7 @@ abstract class PopupWindowController extends BaseWindowController { bool get isActivated { BaseWindowController parent = this.parent; while (true) { - if (parent is RegularWindowController) { + if (parent is WindowController) { return parent.isActivated; } else if (parent is DialogWindowController) { return parent.isActivated; @@ -1217,15 +1217,15 @@ abstract class SatelliteWindowController extends BaseWindowController { /// /// {@macro flutter.widgets.windowing.satelliteConstructorCommon} /// - /// {@macro flutter.widgets.windowing.sizedToContentConstructor} + /// {@macro flutter.widgets.windowing.shrinkWrapConstructor} /// - /// To create a dialog with a specific size instead, use the default + /// To create a satellite window with a specific size instead, use the default /// [SatelliteWindowController] constructor. /// /// {@macro flutter.widgets.windowing.shared} /// /// {@macro flutter.widgets.windowing.experimental} - factory SatelliteWindowController.sizedToContent({ + factory SatelliteWindowController.shrinkWrap({ required BaseWindowController parent, required WindowPositioner initialPositioner, Rect? initialAnchorRect, @@ -1346,16 +1346,16 @@ abstract class SatelliteWindowController extends BaseWindowController { /// {@macro flutter.widgets.windowing.experimental} @internal abstract class WindowingOwner { - /// Creates a [RegularWindowController] with the provided properties. + /// Creates a [WindowController] with the provided properties. /// - /// Most app developers should use [RegularWindowController]'s constructor + /// Most app developers should use [WindowController]'s constructor /// instead of calling this method directly. This method allows platforms /// to inject platform-specific logic. /// /// {@macro flutter.widgets.windowing.experimental} @internal - RegularWindowController createRegularWindowController({ - required RegularWindowControllerDelegate delegate, + WindowController createWindowController({ + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, required bool resizable, @@ -1455,8 +1455,8 @@ class _WindowingOwnerUnsupported extends WindowingOwner { final String errorMessage; @override - RegularWindowController createRegularWindowController({ - required RegularWindowControllerDelegate delegate, + WindowController createWindowController({ + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, bool resizable = true, @@ -1514,18 +1514,18 @@ class _WindowingOwnerUnsupported extends WindowingOwner { } } -/// The [RegularWindow] widget provides a way to render a regular window in the +/// The [Window] widget provides a way to render a regular window in the /// widget tree. /// /// The provided [controller] creates the native window that backs /// the widget. The [child] widget is rendered into this newly created window. /// -/// When a [RegularWindow] widget is removed from the tree, the window that was created +/// When a [Window] widget is removed from the tree, the window that was created /// by the [controller] remains valid until the caller destroys it by calling -/// [RegularWindowController.destroy]. +/// [WindowController.destroy]. /// /// Widgets in the same tree as the [child] widget will have access to the -/// [RegularWindowController] via the [WindowScope] widget. +/// [WindowController] via the [WindowScope] widget. /// /// {@tool snippet} /// An example usage might look like: @@ -1539,8 +1539,8 @@ class _WindowingOwnerUnsupported extends WindowingOwner { /// /// void main() { /// runWidget( -/// RegularWindow( -/// controller: RegularWindowController( +/// Window( +/// controller: WindowController( /// size: const Size(800, 600), /// constraints: const BoxConstraints(minWidth: 640, minHeight: 480), /// title: 'Example Window', @@ -1554,18 +1554,18 @@ class _WindowingOwnerUnsupported extends WindowingOwner { /// /// {@macro flutter.widgets.windowing.experimental} @internal -class RegularWindow extends StatelessWidget { +class Window extends StatelessWidget { /// Creates a regular window widget. /// /// The [controller] creates the native backing window into which the /// [child] widget is rendered. /// /// It is up to the caller to destroy the window by calling - /// [RegularWindowController.destroy] when the window is no longer needed. + /// [WindowController.destroy] when the window is no longer needed. /// /// {@macro flutter.widgets.windowing.experimental} @internal - RegularWindow({super.key, required this.controller, required this.child}) { + Window({super.key, required this.controller, required this.child}) { if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); } @@ -1575,7 +1575,7 @@ class RegularWindow extends StatelessWidget { /// /// {@macro flutter.widgets.windowing.experimental} @internal - final RegularWindowController controller; + final WindowController controller; /// The content rendered into this window. /// @@ -1622,8 +1622,8 @@ class RegularWindow extends StatelessWidget { /// /// void main() { /// runWidget( -/// RegularWindow( -/// controller: RegularWindowController( +/// Window( +/// controller: WindowController( /// size: const Size(800, 600), /// constraints: const BoxConstraints(minWidth: 640, minHeight: 480), /// title: 'Example Window', @@ -1887,7 +1887,7 @@ enum _WindowControllerAspect { /// /// See also: /// -/// * [RegularWindow], the widget to create a regular window. +/// * [Window], the widget to create a regular window. /// * [DialogWindow], the widget to create a dialog window. @internal class WindowScope extends InheritedModel<_WindowControllerAspect> { @@ -1960,9 +1960,9 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController], the controller for regular top-level windows. + /// * [WindowController], the controller for regular top-level windows. /// * [DialogWindowController], the controller for dialog windows. - /// * [RegularWindow], the widget for a regular window. + /// * [Window], the widget for a regular window. /// * [DialogWindow], the widget for a dialog window. /// * [maybeOf], which doesn't throw or assert if it doesn't find a /// [WindowScope] ancestor. It returns null instead. @@ -1977,9 +1977,9 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController], the controller for regular top-level windows. + /// * [WindowController], the controller for regular top-level windows. /// * [DialogWindowController], the controller for dialog windows. - /// * [RegularWindow], the widget for a regular window. + /// * [Window], the widget for a regular window. /// * [DialogWindow], the widget for a dialog window. /// * [of], which will throw if it doesn't find a [WindowScope] ancestor, /// instead of returning null. @@ -2026,7 +2026,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.title], which returns the current title of the window. + /// * [WindowController.title], which returns the current title of the window. /// * [of], which returns the [BaseWindowController] associated with the window. @internal static String titleOf(BuildContext context) { @@ -2039,7 +2039,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.title], which returns the current title of the window. + /// * [WindowController.title], which returns the current title of the window. /// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found. @internal static String? maybeTitleOf(BuildContext context) { @@ -2062,7 +2062,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isActivated], which returns the current activation status of the window. + /// * [WindowController.isActivated], which returns the current activation status of the window. /// * [of], which returns the [BaseWindowController] associated with the window. @internal static bool isActivatedOf(BuildContext context) { @@ -2076,7 +2076,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isActivated], which returns the current activation status of the window. + /// * [WindowController.isActivated], which returns the current activation status of the window. /// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found. @internal static bool? maybeIsActivatedOf(BuildContext context) { @@ -2099,7 +2099,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isMinimized], which returns the current minimized status of the window. + /// * [WindowController.isMinimized], which returns the current minimized status of the window. /// * [of], which returns the [BaseWindowController] associated with the window. @internal static bool isMinimizedOf(BuildContext context) { @@ -2113,7 +2113,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isMinimized], which returns the current minimized status of the window. + /// * [WindowController.isMinimized], which returns the current minimized status of the window. /// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found. @internal static bool? maybeIsMinimizedOf(BuildContext context) { @@ -2136,7 +2136,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isMaximized], which returns the current maximized status of the window. + /// * [WindowController.isMaximized], which returns the current maximized status of the window. /// * [of], which returns the [BaseWindowController] associated with the window. @internal static bool isMaximizedOf(BuildContext context) { @@ -2150,7 +2150,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isMaximized], which returns the current maximized status of the window. + /// * [WindowController.isMaximized], which returns the current maximized status of the window. /// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found. @internal static bool? maybeIsMaximizedOf(BuildContext context) { @@ -2173,7 +2173,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isFullscreen], which returns the current fullscreen status of the window. + /// * [WindowController.isFullscreen], which returns the current fullscreen status of the window. /// * [of], which returns the [BaseWindowController] associated with the window. @internal static bool isFullscreenOf(BuildContext context) { @@ -2187,7 +2187,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// /// See also: /// - /// * [RegularWindowController.isFullscreen], which returns the current fullscreen status of the window. + /// * [WindowController.isFullscreen], which returns the current fullscreen status of the window. /// * [maybeOf], which returns the [BaseWindowController] associated with the window, or null if not found. @internal static bool? maybeIsFullscreenOf(BuildContext context) { @@ -2234,7 +2234,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// given [controller]. Controllers that do not support titles report an empty /// string. static String _titleValue(BaseWindowController controller) => switch (controller) { - RegularWindowController() => controller.title, + WindowController() => controller.title, DialogWindowController() => controller.title, TooltipWindowController() => '', PopupWindowController() => '', @@ -2244,7 +2244,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { // Computes the value of the [_WindowControllerAspect.activated] aspect for the // given [controller]. Controllers that do not support activation report false. static bool _isActivatedValue(BaseWindowController controller) => switch (controller) { - RegularWindowController() => controller.isActivated, + WindowController() => controller.isActivated, DialogWindowController() => controller.isActivated, TooltipWindowController() => false, PopupWindowController() => controller.isActivated, @@ -2255,7 +2255,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// given [controller]. Controllers that do not support maximization report /// false. static bool _isMaximizedValue(BaseWindowController controller) => switch (controller) { - RegularWindowController() => controller.isMaximized, + WindowController() => controller.isMaximized, DialogWindowController() => false, TooltipWindowController() => false, PopupWindowController() => false, @@ -2266,7 +2266,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// given [controller]. Controllers that do not support minimization report /// false. static bool _isMinimizedValue(BaseWindowController controller) => switch (controller) { - RegularWindowController() => controller.isMinimized, + WindowController() => controller.isMinimized, DialogWindowController() => controller.isMinimized, TooltipWindowController() => false, PopupWindowController() => false, @@ -2277,7 +2277,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// the given [controller]. Controllers that do not support fullscreen report /// false. static bool _isFullscreenValue(BaseWindowController controller) => switch (controller) { - RegularWindowController() => controller.isFullscreen, + WindowController() => controller.isFullscreen, DialogWindowController() => false, TooltipWindowController() => false, PopupWindowController() => false, @@ -2314,7 +2314,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { ErrorHint( 'No WindowScope ancestor could be found starting from the context ' 'that was passed to WindowScope.of(). This can happen because the ' - 'context used is not a descendant of a RegularWindow widget, which introduces ' + 'context used is not a descendant of a Window widget, which introduces ' 'a WindowScope.', ), ]); @@ -2549,7 +2549,7 @@ class WindowEntry { /// {@tool dartpad} /// An example usage might look like this, where the window manager wraps /// the root of the widget tree so that dialogs can be rendered at the same level -/// as a [RegularWindow]. +/// as a [Window]. /// /// ** See code in examples/api/lib/widgets/windows/window_manager.0.dart ** /// {@end-tool} @@ -2598,7 +2598,7 @@ class _WindowManagerState extends State { controller: dialog, child: entry.builder(context), ), - final RegularWindowController regular => RegularWindow( + final WindowController regular => Window( controller: regular, child: entry.builder(context), ), diff --git a/packages/flutter/lib/src/widgets/_window_linux.dart b/packages/flutter/lib/src/widgets/_window_linux.dart index 228a88759e4d5..b94afc6b1a748 100644 --- a/packages/flutter/lib/src/widgets/_window_linux.dart +++ b/packages/flutter/lib/src/widgets/_window_linux.dart @@ -96,14 +96,14 @@ class WindowingOwnerLinux extends WindowingOwner { @internal @override - RegularWindowController createRegularWindowController({ + WindowController createWindowController({ Size? size, BoxConstraints? constraints, required bool resizable, String? title, - required RegularWindowControllerDelegate delegate, + required WindowControllerDelegate delegate, }) { - final controller = RegularWindowControllerLinux( + final controller = WindowControllerLinux( owner: this, delegate: delegate, size: size, @@ -269,7 +269,7 @@ class LinuxWindowRegistrar { /// /// {@macro flutter.widgets.windowing.experimental} @internal -abstract interface class WindowControllerLinux { +abstract interface class BaseWindowControllerLinux { /// Returns pointer to the underlying [GtkWindow](https://docs.gtk.org/gtk3/class.Window.html). /// /// Using this pointer implies the user is aware of any side effects changes may have to Flutter behavior. @@ -294,15 +294,15 @@ abstract interface class WindowControllerLinux { ffi.Pointer get flutterViewHandle; } -/// Implementation of [RegularWindowController] for the Linux platform. +/// Implementation of [WindowController] for the Linux platform. /// /// {@macro flutter.widgets.windowing.experimental} /// /// See also: /// -/// * [RegularWindowController], the base class for regular windows. -class RegularWindowControllerLinux extends RegularWindowController - implements WindowControllerLinux { +/// * [WindowController], the base class for regular windows. +class WindowControllerLinux extends WindowController + implements BaseWindowControllerLinux { /// Creates a new regular window controller for Linux. /// /// When this constructor completes the native window has been created and @@ -312,11 +312,11 @@ class RegularWindowControllerLinux extends RegularWindowController /// /// See also: /// - /// * [RegularWindowController], the base class for regular windows. + /// * [WindowController], the base class for regular windows. @internal - RegularWindowControllerLinux({ + WindowControllerLinux({ required WindowingOwnerLinux owner, - required RegularWindowControllerDelegate delegate, + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, String? title, @@ -370,7 +370,7 @@ class RegularWindowControllerLinux extends RegularWindowController } final WindowingOwnerLinux _owner; - final RegularWindowControllerDelegate _delegate; + final WindowControllerDelegate _delegate; final _GtkWindow _window; late final _FlView _view; late final _FlViewMonitor _viewMonitor; @@ -509,7 +509,7 @@ class RegularWindowControllerLinux extends RegularWindowController /// See also: /// /// * [DialogWindowController], the base class for dialog windows. -class DialogWindowControllerLinux extends DialogWindowController implements WindowControllerLinux { +class DialogWindowControllerLinux extends DialogWindowController implements BaseWindowControllerLinux { /// Creates a new dialog window controller for Linux. /// /// When this constructor completes the native window has been created and @@ -700,7 +700,7 @@ class DialogWindowControllerLinux extends DialogWindowController implements Wind /// /// * [TooltipWindowController], the base class for tooltip windows. class TooltipWindowControllerLinux extends TooltipWindowController - implements WindowControllerLinux { + implements BaseWindowControllerLinux { /// Creates a new tooltip window controller for Linux. /// /// When this constructor completes the native window has been created and @@ -895,7 +895,7 @@ class TooltipWindowControllerLinux extends TooltipWindowController /// See also: /// /// * [PopupWindowController], the base class for popup windows. -class PopupWindowControllerLinux extends PopupWindowController implements WindowControllerLinux { +class PopupWindowControllerLinux extends PopupWindowController implements BaseWindowControllerLinux { /// Creates a new popup window controller for Linux. /// /// When this constructor completes the native window has been created and diff --git a/packages/flutter/lib/src/widgets/_window_macos.dart b/packages/flutter/lib/src/widgets/_window_macos.dart index 7066805e7a735..438e9636105d2 100644 --- a/packages/flutter/lib/src/widgets/_window_macos.dart +++ b/packages/flutter/lib/src/widgets/_window_macos.dart @@ -75,14 +75,14 @@ class WindowingOwnerMacOS extends WindowingOwner { } @override - RegularWindowController createRegularWindowController({ - required RegularWindowControllerDelegate delegate, + WindowController createWindowController({ + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, required bool resizable, String? title, }) { - final controller = RegularWindowControllerMacOS( + final controller = WindowControllerMacOS( owner: this, delegate: delegate, size: size, @@ -187,7 +187,7 @@ class WindowingOwnerMacOS extends WindowingOwner { /// /// {@macro flutter.widgets.windowing.experimental} @internal -abstract interface class WindowControllerMacOS { +abstract interface class BaseWindowControllerMacOS { /// Returns pointer to the underlying NSWindow. /// /// Using this pointer implies the user is aware of any side effects changes may have to Flutter behavior. @@ -206,7 +206,7 @@ abstract interface class WindowControllerMacOS { bool get isDestroyed; } -mixin _WindowControllerMixin implements WindowControllerMacOS { +mixin _WindowControllerMixin implements BaseWindowControllerMacOS { void _initController(WindowingOwnerMacOS owner) { if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); @@ -501,19 +501,19 @@ class PopupWindowControllerMacOS extends PopupWindowController with _WindowContr Rect _anchorRect; } -/// Implementation of [RegularWindowController] for the macOS platform. +/// Implementation of [WindowController] for the macOS platform. /// /// {@macro flutter.widgets.windowing.experimental} /// /// See also: /// -/// * [RegularWindowController], the base class for regular windows. -class RegularWindowControllerMacOS extends RegularWindowController with _WindowControllerMixin { +/// * [WindowController], the base class for regular windows. +class WindowControllerMacOS extends WindowController with _WindowControllerMixin { /// Creates a new regular window controller for macOS. When this constructor /// completes the FlutterView is created and framework is aware of it. - RegularWindowControllerMacOS({ + WindowControllerMacOS({ required WindowingOwnerMacOS owner, - required RegularWindowControllerDelegate delegate, + required WindowControllerDelegate delegate, required Size? size, BoxConstraints? constraints, String? title, @@ -521,7 +521,7 @@ class RegularWindowControllerMacOS extends RegularWindowController with _WindowC super.empty() { _initController(owner); - final int viewId = _MacOSPlatformInterface.createRegularWindow( + final int viewId = _MacOSPlatformInterface.createWindow( size: size, constraints: constraints, onShouldClose: _onShouldClose.nativeFunction, @@ -627,7 +627,7 @@ class RegularWindowControllerMacOS extends RegularWindowController with _WindowC return _MacOSPlatformInterface.isFullscreen(windowHandle); } - final RegularWindowControllerDelegate _delegate; + final WindowControllerDelegate _delegate; @override bool get isActivated => _MacOSPlatformInterface.isActivated(windowHandle); @@ -883,10 +883,10 @@ class _MacOSPlatformInterface { @Native)>( symbol: 'InternalFlutter_WindowController_CreateRegularWindow', ) - external static int _createRegularWindow(int engineId, Pointer<_WindowCreationRequest> request); + external static int _createWindow(int engineId, Pointer<_WindowCreationRequest> request); /// Creates a new window and returns the viewId of the created FlutterView. - static int createRegularWindow({ + static int createWindow({ required Size? size, BoxConstraints? constraints, required Pointer> onShouldClose, @@ -913,7 +913,7 @@ class _MacOSPlatformInterface { ..constraints.maxWidth = constraints.maxWidth ..constraints.maxHeight = constraints.maxHeight; } - final int viewId = _createRegularWindow( + final int viewId = _createWindow( WidgetsBinding.instance.platformDispatcher.engineId!, request, ); diff --git a/packages/flutter/lib/src/widgets/_window_win32.dart b/packages/flutter/lib/src/widgets/_window_win32.dart index 4e25c82a131d9..0e9d3db33c7a1 100644 --- a/packages/flutter/lib/src/widgets/_window_win32.dart +++ b/packages/flutter/lib/src/widgets/_window_win32.dart @@ -139,14 +139,14 @@ class WindowingOwnerWin32 extends WindowingOwner { @internal @override - RegularWindowController createRegularWindowController({ + WindowController createWindowController({ Size? size, BoxConstraints? constraints, required bool resizable, String? title, - required RegularWindowControllerDelegate delegate, + required WindowControllerDelegate delegate, }) { - return RegularWindowControllerWin32( + return WindowControllerWin32( owner: this, delegate: delegate, size: size, @@ -283,10 +283,10 @@ class WindowingOwnerWin32 extends WindowingOwner { } } -class _RegularWindowMesageHandler implements _WindowsMessageHandler { - _RegularWindowMesageHandler({required this.controller}); +class _WindowMessageHandler implements _WindowsMessageHandler { + _WindowMessageHandler({required this.controller}); - final RegularWindowControllerWin32 controller; + final WindowControllerWin32 controller; @override int? handleWindowsMessage( @@ -304,7 +304,7 @@ class _RegularWindowMesageHandler implements _WindowsMessageHandler { /// /// {@macro flutter.widgets.windowing.experimental} @internal -abstract mixin class WindowControllerWin32 { +abstract mixin class BaseWindowControllerWin32 { /// Returns the underlying HWND for this window. /// /// Using this handle implies the user is aware of any side effects changes may have to Flutter behavior. @@ -317,14 +317,14 @@ abstract mixin class WindowControllerWin32 { HWND get windowHandle; } -/// Implementation of [RegularWindowController] for the Windows platform. +/// Implementation of [WindowController] for the Windows platform. /// /// {@macro flutter.widgets.windowing.experimental} /// /// See also: /// -/// * [RegularWindowController], the base class for regular windows. -class RegularWindowControllerWin32 extends RegularWindowController with WindowControllerWin32 { +/// * [WindowController], the base class for regular windows. +class WindowControllerWin32 extends WindowController with BaseWindowControllerWin32 { /// Creates a new regular window controller for Win32. /// /// When this constructor completes the native window has been created and @@ -334,11 +334,11 @@ class RegularWindowControllerWin32 extends RegularWindowController with WindowCo /// /// See also: /// - /// * [RegularWindowController], the base class for regular windows. + /// * [WindowController], the base class for regular windows. @internal - RegularWindowControllerWin32({ + WindowControllerWin32({ required WindowingOwnerWin32 owner, - required RegularWindowControllerDelegate delegate, + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, String? title, @@ -349,16 +349,16 @@ class RegularWindowControllerWin32 extends RegularWindowController with WindowCo if (!isWindowingEnabled) { throw UnsupportedError(_kWindowingDisabledErrorMessage); } - _handler = _RegularWindowMesageHandler(controller: this); + _handler = _WindowMessageHandler(controller: this); owner._addMessageHandler(_handler); - final sizedToContent = size == null; - final int viewId = _Win32PlatformInterface.createRegularWindow( + final shrinkWrap = size == null; + final int viewId = _Win32PlatformInterface.createWindow( _owner.allocator, WidgetsBinding.instance.platformDispatcher.engineId!, size, constraints, title, - sizedToContent, + shrinkWrap, resizable, ); if (viewId < 0) { @@ -372,8 +372,8 @@ class RegularWindowControllerWin32 extends RegularWindowController with WindowCo } final WindowingOwnerWin32 _owner; - final RegularWindowControllerDelegate _delegate; - late final _RegularWindowMesageHandler _handler; + final WindowControllerDelegate _delegate; + late final _WindowMessageHandler _handler; bool _destroyed = false; @override @@ -570,7 +570,7 @@ class _DialogWindowMesageHandler implements _WindowsMessageHandler { /// See also: /// /// * [DialogWindowController], the base class for dialog windows. -class DialogWindowControllerWin32 extends DialogWindowController with WindowControllerWin32 { +class DialogWindowControllerWin32 extends DialogWindowController with BaseWindowControllerWin32 { /// Creates a new dialog window controller for Win32. /// /// When this constructor completes the native window has been created and @@ -599,7 +599,7 @@ class DialogWindowControllerWin32 extends DialogWindowController with WindowCont } _handler = _DialogWindowMesageHandler(controller: this); owner._addMessageHandler(_handler); - final sizedToContent = size == null; + final shrinkWrap = size == null; final int viewId = _Win32PlatformInterface.createDialogWindow( _owner.allocator, WidgetsBinding.instance.platformDispatcher.engineId!, @@ -612,7 +612,7 @@ class DialogWindowControllerWin32 extends DialogWindowController with WindowCont parent.rootView.viewId, ) : null, - sizedToContent, + shrinkWrap, resizable, ); if (viewId < 0) { @@ -787,7 +787,7 @@ typedef _GetWindowPositionNative = /// /// * [TooltipWindowController], the base class for tooltip windows. class TooltipWindowControllerWin32 extends TooltipWindowController - with WindowControllerWin32 + with BaseWindowControllerWin32 implements _WindowsMessageHandler { /// Creates a new tooltip window controller for Win32. /// @@ -1322,35 +1322,35 @@ class _Win32PlatformInterface { ffi.Pointer<_WindowingInitRequest> request, ); - static int createRegularWindow( + static int createWindow( ffi.Allocator allocator, int engineId, Size? size, BoxConstraints? constraints, String? title, - bool sizedToContent, + bool shrinkWrap, bool resizable, ) { - final ffi.Pointer<_RegularWindowCreationRequest> request = - allocator<_RegularWindowCreationRequest>(); + final ffi.Pointer<_WindowCreationRequest> request = + allocator<_WindowCreationRequest>(); try { request.ref.size.from(size); request.ref.constraints.from(constraints); - request.ref.title = (title ?? 'Regular window').toNativeUtf16(allocator: allocator); - request.ref.sizedToContent = sizedToContent; + request.ref.title = (title ?? 'Window').toNativeUtf16(allocator: allocator); + request.ref.shrinkWrap = shrinkWrap; request.ref.resizable = resizable; - return _createRegularWindow(engineId, request); + return _createWindow(engineId, request); } finally { allocator.free(request); } } - @ffi.Native)>( + @ffi.Native)>( symbol: 'InternalFlutterWindows_WindowManager_CreateRegularWindow', ) - external static int _createRegularWindow( + external static int _createWindow( int engineId, - ffi.Pointer<_RegularWindowCreationRequest> request, + ffi.Pointer<_WindowCreationRequest> request, ); static int createDialogWindow( @@ -1360,7 +1360,7 @@ class _Win32PlatformInterface { BoxConstraints? constraints, String? title, HWND? parent, - bool sizedToContent, + bool shrinkWrap, bool resizable, ) { final ffi.Pointer<_DialogWindowCreationRequest> request = @@ -1370,7 +1370,7 @@ class _Win32PlatformInterface { request.ref.constraints.from(constraints); request.ref.title = (title ?? 'Dialog window').toNativeUtf16(allocator: allocator); request.ref.parentOrNull = parent ?? ffi.Pointer.fromAddress(0); - request.ref.sizedToContent = sizedToContent; + request.ref.shrinkWrap = shrinkWrap; request.ref.resizable = resizable; return _createDialogWindow(engineId, request); } finally { @@ -1637,14 +1637,14 @@ class _Win32PlatformInterface { } } -/// Payload for the creation method used by [_Win32PlatformInterface.createRegularWindow]. -final class _RegularWindowCreationRequest extends ffi.Struct { +/// Payload for the creation method used by [_Win32PlatformInterface.createWindow]. +final class _WindowCreationRequest extends ffi.Struct { external _WindowSizeRequest size; external _WindowConstraintsRequest constraints; external ffi.Pointer<_Utf16> title; @ffi.Bool() - external bool sizedToContent; + external bool shrinkWrap; @ffi.Bool() external bool resizable; @@ -1658,7 +1658,7 @@ final class _DialogWindowCreationRequest extends ffi.Struct { external HWND parentOrNull; @ffi.Bool() - external bool sizedToContent; + external bool shrinkWrap; @ffi.Bool() external bool resizable; @@ -1701,7 +1701,7 @@ final class _WindowingInitRequest extends ffi.Struct { onMessage; } -/// Payload for the size of a window used by [_RegularWindowCreationRequest] and +/// Payload for the size of a window used by [_WindowCreationRequest] and /// [_Win32PlatformInterface.setWindowContentSize]. final class _WindowSizeRequest extends ffi.Struct { @ffi.Bool() @@ -1720,7 +1720,7 @@ final class _WindowSizeRequest extends ffi.Struct { } } -/// Payload for the constraints of a window used by [_RegularWindowCreationRequest] and +/// Payload for the constraints of a window used by [_WindowCreationRequest] and /// [_Win32PlatformInterface.setWindowConstraints]. final class _WindowConstraintsRequest extends ffi.Struct { @ffi.Bool() diff --git a/packages/flutter/test/widgets/windowing_test.dart b/packages/flutter/test/widgets/windowing_test.dart index 7e50b9beccd11..b0b96540bed5b 100644 --- a/packages/flutter/test/widgets/windowing_test.dart +++ b/packages/flutter/test/widgets/windowing_test.dart @@ -12,13 +12,13 @@ import 'package:flutter/src/widgets/_window.dart' DialogWindowControllerDelegate, PopupWindow, PopupWindowController, - RegularWindow, - RegularWindowController, - RegularWindowControllerDelegate, SatelliteWindow, SatelliteWindowController, TooltipWindow, TooltipWindowController, + Window, + WindowController, + WindowControllerDelegate, WindowScope, WindowingOwner, createDefaultWindowingOwner; @@ -28,8 +28,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'multi_view_testing.dart'; -class _StubRegularWindowController extends RegularWindowController { - _StubRegularWindowController(WidgetTester tester) : super.empty() { +class _StubWindowController extends WindowController { + _StubWindowController(WidgetTester tester) : super.empty() { rootView = FakeView(tester.view); } @@ -135,7 +135,7 @@ class _StubTooltipWindowController extends TooltipWindowController { final WidgetTester tester; @override - BaseWindowController get parent => _StubRegularWindowController(tester); + BaseWindowController get parent => _StubWindowController(tester); @override Size get contentSize => Size.zero; @@ -164,7 +164,7 @@ class _StubPopupWindowController extends PopupWindowController { final WidgetTester tester; @override - BaseWindowController get parent => _StubRegularWindowController(tester); + BaseWindowController get parent => _StubWindowController(tester); @override Size get contentSize => Size.zero; @@ -196,7 +196,7 @@ class _StubSatelliteWindowController extends SatelliteWindowController { final WidgetTester tester; @override - BaseWindowController get parent => _StubRegularWindowController(tester); + BaseWindowController get parent => _StubWindowController(tester); @override Size get contentSize => Size.zero; @@ -232,11 +232,11 @@ class _StubSatelliteWindowController extends SatelliteWindowController { } } -// A controller that mutates its aspect values and notifies listeners, and whose -// value getters throw once the window is destroyed, mirroring the behavior of -// the real platform controllers. -class _MutableRegularWindowController extends RegularWindowController { - _MutableRegularWindowController(WidgetTester tester) : super.empty() { +// A controller that mutates its aspect values and notifies listeners, used to +// verify that dependents rebuild when the controller notifies even though the +// same controller instance is reused across rebuilds. +class _MutableWindowController extends WindowController { + _MutableWindowController(WidgetTester tester) : super.empty() { rootView = FakeView(tester.view); } @@ -342,13 +342,10 @@ void main() { expect(owner, isA()); }); - test('default WindowingOwner throws when accessing createRegularWindowController', () { + test('default WindowingOwner throws when accessing createWindowController', () { final WindowingOwner owner = createDefaultWindowingOwner(); expect( - () => owner.createRegularWindowController( - delegate: RegularWindowControllerDelegate(), - resizable: true, - ), + () => owner.createWindowController(delegate: WindowControllerDelegate(), resizable: true), throwsUnsupportedError, ); }); @@ -417,12 +414,12 @@ void main() { isWindowingEnabled = true; }); - testWidgets('RegularWindow does not throw', (WidgetTester tester) async { - final controller = _StubRegularWindowController(tester); + testWidgets('Window does not throw', (WidgetTester tester) async { + final controller = _StubWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow(controller: controller, child: Container()), + Window(controller: controller, child: Container()), ); }); @@ -436,12 +433,12 @@ void main() { }); testWidgets('Can access WindowScope.of for regular windows', (WidgetTester tester) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); BaseWindowController? scope; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -452,7 +449,7 @@ void main() { ), ); - expect(scope, isA()); + expect(scope, isA()); }); testWidgets('Can access WindowScope.of for dialog windows', (WidgetTester tester) async { @@ -538,12 +535,12 @@ void main() { testWidgets('Can access WindowScope.maybeOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); BaseWindowController? scope; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -554,7 +551,7 @@ void main() { ), ); - expect(scope, isA()); + expect(scope, isA()); }); testWidgets('Can access WindowScope.maybeOf for dialog windows', (WidgetTester tester) async { @@ -644,12 +641,12 @@ void main() { testWidgets('Can access WindowScope.contentSizeOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); Size? size; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -754,12 +751,12 @@ void main() { testWidgets('Can access WindowScope.maybeContentSizeOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); Size? size; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -864,12 +861,12 @@ void main() { testWidgets('Can access WindowScope.titleOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); String? title; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -970,12 +967,12 @@ void main() { testWidgets('Can access WindowScope.maybeTitleOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); String? title; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1080,12 +1077,12 @@ void main() { testWidgets('Can access WindowScope.isActivatedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isActivated; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1190,12 +1187,12 @@ void main() { testWidgets('Can access WindowScope.maybeIsActivatedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isActivated; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1300,12 +1297,12 @@ void main() { testWidgets('Can access WindowScope.isMinimizedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isMinimized; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1388,12 +1385,12 @@ void main() { testWidgets('Can access WindowScope.maybeIsMinimizedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isMinimized; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1476,12 +1473,12 @@ void main() { testWidgets('Can access WindowScope.isMaximizedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isMaximized; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1586,12 +1583,12 @@ void main() { testWidgets('Can access WindowScope.maybeIsMaximizedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isMaximized; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1696,12 +1693,12 @@ void main() { testWidgets('Can access WindowScope.isFullscreenOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isFullscreen; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1806,12 +1803,12 @@ void main() { testWidgets('Can access WindowScope.maybeIsFullscreenOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isFullscreen; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1916,12 +1913,12 @@ void main() { testWidgets('Dependent rebuilds when an aspect changes and the controller notifies', ( WidgetTester tester, ) async { - final controller = _MutableRegularWindowController(tester); + final controller = _MutableWindowController(tester); addTearDown(controller.dispose); final observed = []; await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -1943,12 +1940,12 @@ void main() { testWidgets('Can access WindowScope.isDestroyedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isDestroyed; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -2053,12 +2050,12 @@ void main() { testWidgets('Can access WindowScope.maybeIsDestroyedOf for regular windows', ( WidgetTester tester, ) async { - final controller = _StubRegularWindowController(tester); + final controller = _StubWindowController(tester); bool? isDestroyed; addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { @@ -2180,12 +2177,12 @@ void main() { }); testWidgets('Dependent rebuilds when the window is destroyed', (WidgetTester tester) async { - final controller = _MutableRegularWindowController(tester); + final controller = _MutableWindowController(tester); addTearDown(controller.dispose); final observed = []; await tester.pumpWidget( wrapWithView: false, - RegularWindow( + Window( controller: controller, child: Builder( builder: (BuildContext context) { diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index bd6237e2159fb..caf47ca30d6da 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -273,12 +273,12 @@ mixin _ChildWindowHierarchyMixin { var foundPopup = false; for (final BaseWindowController child in _children) { switch (child) { - case final RegularWindowController regularChild: + case final WindowController regularChild: if (foundPopup) { // Already found a popup, skip anything else. break; } - activateable = (regularChild as _TestRegularWindowController).getFirstActivatableChild(); + activateable = (regularChild as _TestWindowController).getFirstActivatableChild(); case final DialogWindowController dialogChild: // Always return the first dialog found. return (dialogChild as _TestDialogWindowController).getFirstActivatableChild(); @@ -305,9 +305,9 @@ mixin _ChildWindowHierarchyMixin { } } -class _TestRegularWindowController extends RegularWindowController with _ChildWindowHierarchyMixin { - _TestRegularWindowController({ - required RegularWindowControllerDelegate delegate, +class _TestWindowController extends WindowController with _ChildWindowHierarchyMixin { + _TestWindowController({ + required WindowControllerDelegate delegate, required TestPlatformDispatcher platformDispatcher, required this.windowingOwner, Size? size, @@ -329,7 +329,7 @@ class _TestRegularWindowController extends RegularWindowController with _ChildWi activate(); } - final RegularWindowControllerDelegate _delegate; + final WindowControllerDelegate _delegate; final _TestWindowingOwner windowingOwner; Size _size; BoxConstraints _constraints; @@ -435,8 +435,8 @@ void _addChildToParent(BaseWindowController? parent, BaseWindowController child) switch (parent) { case final DialogWindowController testParent: (testParent as _TestDialogWindowController).addChild(child); - case final RegularWindowController testParent: - (testParent as _TestRegularWindowController).addChild(child); + case final WindowController testParent: + (testParent as _TestWindowController).addChild(child); case final PopupWindowController testParent: (testParent as _TestPopupWindowController).addChild(child); case final SatelliteWindowController testParent: @@ -452,8 +452,8 @@ void _removeChildFromParent(BaseWindowController? parent, BaseWindowController c switch (parent) { case final DialogWindowController testParent: (testParent as _TestDialogWindowController).removeChild(child); - case final RegularWindowController testParent: - (testParent as _TestRegularWindowController).removeChild(child); + case final WindowController testParent: + (testParent as _TestWindowController).removeChild(child); case final PopupWindowController testParent: (testParent as _TestPopupWindowController).removeChild(child); case final SatelliteWindowController testParent: @@ -856,8 +856,8 @@ class _TestWindowingOwner extends WindowingOwner { /// Returns the activated [BaseWindowController]. BaseWindowController activateWindowController(BaseWindowController controller) { switch (controller) { - case final RegularWindowController regularController: - final BaseWindowController leaf = (regularController as _TestRegularWindowController) + case final WindowController regularController: + final BaseWindowController leaf = (regularController as _TestWindowController) .getFirstActivatableChild(); _activeWindowController = leaf; return _activeWindowController!; @@ -887,7 +887,7 @@ class _TestWindowingOwner extends WindowingOwner { } switch (parent) { - case final RegularWindowController regularParent: + case final WindowController regularParent: regularParent.activate(); case final DialogWindowController dialogParent: dialogParent.activate(); @@ -917,7 +917,7 @@ class _TestWindowingOwner extends WindowingOwner { } switch (controller) { - case final RegularWindowController _: + case final WindowController _: _activeWindowController = null; case final DialogWindowController dialogController: if (!_tryActivateParent(dialogController.parent)) { @@ -946,14 +946,14 @@ class _TestWindowingOwner extends WindowingOwner { @internal @override - RegularWindowController createRegularWindowController({ - required RegularWindowControllerDelegate delegate, + WindowController createWindowController({ + required WindowControllerDelegate delegate, Size? size, BoxConstraints? constraints, required bool resizable, String? title, }) { - return _TestRegularWindowController( + return _TestWindowController( delegate: delegate, platformDispatcher: _platformDispatcher, windowingOwner: this, From eb5f7d8a4956121ac10a1a4f16c1cccf931c2367 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 13:51:26 -0400 Subject: [PATCH 043/147] Roll Dart SDK from e3fc57eae9eb to 153abd9a8296 (3 revisions) (#189987) https://dart.googlesource.com/sdk.git/+log/e3fc57eae9eb..153abd9a8296 2026-07-24 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-63.0.dev 2026-07-24 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-62.0.dev 2026-07-24 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-61.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jsimmons@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 717f6623edcc2..ec0d8a1e5ebfe 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': 'e3fc57eae9eb823fd8430f250586d7a1af7d611f', + 'dart_revision': '153abd9a8296ba4bb077c96267974e8540f544e8', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From a74d9b73b8c06cadbcb570e85593834dd482c480 Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Fri, 24 Jul 2026 11:14:21 -0700 Subject: [PATCH 044/147] docs(contributing): add git worktree and rebase workflow guide (#189992) Documentation for worktree and other `git` features. Making an internal doc public. --- docs/contributing/git-worktrees.md | 377 +++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 docs/contributing/git-worktrees.md diff --git a/docs/contributing/git-worktrees.md b/docs/contributing/git-worktrees.md new file mode 100644 index 0000000000000..d647a20513eee --- /dev/null +++ b/docs/contributing/git-worktrees.md @@ -0,0 +1,377 @@ +--- +type: Contributor Doc +title: Git & Git Worktree Workflow Guide for Flutter Development +description: How to efficiently manage multiple concurrent branches and pull requests using Git worktrees. +resource: A practical guide to leveraging Git worktrees in Flutter development for tracking multiple release channels (master, beta, stable), managing concurrent feature branches, and conducting PR reviews without duplicating repository clones or managing git stashes. +tags: [git, worktrees, contributing, branches, PR review, feature development] +timestamp: 2026-07-23T15:26:31-0700 +--- + +# Git & Worktree Workflow Guide for Flutter Development + +Traditional `git` development follows a simple pattern for smaller projects: clone, commit, push, update. Medium projects tend to add branching and release workflows. Flutter is a large project with many moving parts and engineers wearing different hats at different times: framework, engine, infrastructure, release, android, iOS, gardener, sheriff, triage. + +This document covers a recommended workflow that should be flexible for all engineers contributing to Flutter, reduce cloned repository size and wasted space, and provide guidance for common tasks. + +## Setup + +Having one [flutter development environment](https://github.com/flutter/flutter/blob/main/docs/contributing/Setting-up-the-Framework-development-environment.md#set-up-your-environment) incurs a cost when changing branches between master, stable, and feature development - triggering a redownload of flutter artifacts, recompiling the flutter tool, and oftentimes requiring stashing of work. Running third party tools (vscode, dart analyzer, or agent) can also run into trouble when the local branch is changed. Having multiple git clones requires duplication of the `.git` folder for the same source code, juggling environment path changes or using OS aliasing techniques to call the correct version of flutter. + +Instead, we use git worktrees: a way to manage multiple working directories, each with a different branch checked out, all linked to the same Git repository. Using [flutter_worktree](https://github.com/jtmcdole/flutter_worktree#installation) we can end up with a flutter tree setup to track upstream/origin correctly, and have master/stable already checked out. + +```shell +~/flutter/ + ├── .bare/ (The bare repo or main tracking folder) + ├── .git (pointer to .bare/) + ├── fswitch.sh ("fswitch" command line with tab completion - win32:.ps1) + ├── master/ (Always clean, main development branch) + ├── stable/ (Locked to latest stable release for repros) + ├── review-179637/ (Temporary folder for reviewing PR #1234) + └── feature/new-widget/ (Long-running feature work) +``` + +This has the benefit of sharing the `.bare` git repository amongst all trees. The source files for each branch must be *unpacked* from `.bare` and the `/bin/cached` binaries are downloaded to the tree if you run the flutter tool. The install script for flutter_worktree will create the `fswitch.sh` file which you can use in your bash/zsh/windows profile to quickly `fswitch ` and update your environment's path without re-downloading (on Windows, Linux, and macOS). + +With the following directory structure; and with `fswitch.sh` sourced in your profile (e.g. `~/.zshrc`), you can now have multiple terminals open and dedicated to master and stable with a simple `fswitch master` / `fswitch stable`. This updates the current shell session’s OS PATH search to point to `master/bin` or `stable/bin`, allowing for VsCode, Antigravity, and other tooling to find the right version of Flutter for tooling. + +```shell +--- /Users/codefu/src/flutter --- + 1.9 GiB [#############################] /master + 932.7 MiB [############## ] /stable + 738.2 MiB [########### ] /pr-review + 487.0 MiB [####### ] /.bare + 8.0 KiB [ ] fswitch.sh + 4.0 KiB [ ] .git +``` + +> [!WARNING] +> Do not run tools like vscode from the root folder as this can cause them to stall trying to index all of the worktrees. + +### Common Scenarios + +> [!NOTE] +> This document follows the Flutter conventions for naming remotes: +> +> * **upstream**: `git@github.com:flutter/flutter.git` (Fetch target for master/beta/stable) +> * **origin**: `git@github.com:/flutter.git` (Push target for feature PRs) + +#### Code Reviews + +Instead of doing code reviews on [github.com](https://github.com) and lacking the feature rich dart analyzer, or being able to use an agent and other advanced tooling; check out the PR locally. + +Without the `gh` tool: +```shell +# 1. Fetch the PR HEAD +git fetch upstream pull/189954/head:pr-189954 + +# 2. Checkout the PR - automatically follows pull/189954/head +git worktree add pr-189954 + +# 3. Perform the review +cd pr-189954 + +# git log HEAD -n1 +# commit d277b8d7652b1baaf5844dc70040c60914b9eb5c (HEAD -> pr-189954) +# Author: engine-flutter-autoroll +# Date: Thu Jul 23 21:35:02 2026 +0000 + +# ... vscode, vim, antigravity. + +# 4. Delete the worktree when done. Remember to change to any other worktree or the root (with the `.bare` folder) +git worktree remove pr-189954 +``` + +With the `gh` tool it's easier: + +```shell +git worktree add prreview +cd prreview +gh pr checkout 189954 +``` + +#### New Feature Development + +You picked up a feature to implement a new widget, assign yourself the issue, and start work: + +```shell +# Start a new worktree branched from latest HEAD +git worktree add feature/new-widget + +# Switch and start work +cd feature/new-widget +code . + +# Push it upstream +git push --set-upstream origin $(git branch --show-current) + +# ... get reviews, make changes, pass tests + +# Delete the tree when done +git worktree remove feature/new-widget +``` + +There's no need to stash or upload changes until you are ready. You can update master / stable / other feature branches without losing context in `feature/new-widget`. + +#### Bug Investigation (Stable) + +If you've picked up a well documented issue to investigate; you can simply `fswitch stable` and test against stable. + +```shell +# Switch to stable branch +fswitch stable +# Or if you want to bisect starting at some baseref +git worktree add bugHunt baseref +fswitch bugHunt # updates the binaries in the environment PATH +cd bugHunt # changes to the bugHunt worktree +# ... git bisect +# Get the reprocode from the issue +mkdir repro_code && .... + +# Debug however you need to +flutter run + +# Alternate to master and see if its already fixed? +fswitch master +flutter clean +flutter run + +# You could even check out an old stable +cd ~/src/flutter +git worktree add beta upstream/beta +fswitch beta +flutter clean +flutter run +``` + +If you verify the bug and want to start a new PR; you can just create a worktree like a feature rather than juggling changes in your stable/master branch. + +## Modern Git Commands + +Git commands you should be using (and why). + +### Git Switch + +`git branch` is for managing branches (creating, listing, deleting) - while `git switch` is for **navigating** **between** them. Prior to 2019, we would use `git checkout` to switch between branches, but this is an overloaded command and leads to confusion…. E.g. `switch` operates on branches whereas `checkout` can operate on files (`git checkout -- file.txt`) + +```shell +# Create and change to new feature +git switch -c new-feature + +# Just switch +git switch master + +# House keeping +git branch -d new-feature +``` + +> [!NOTE] +> If `branch` is already checked out in another worktree, git will refuse to switch to it with a message like: +> +> ```shell +> fatal: 'master' is already used by worktree at '/Users/codefu/src/flutter/master' +> ``` + +### Git Restore + +Instead of using `git checkout` as a swiss army knife; use `git restore`: + +If you edit a file but realize you made a mess and just want it back to how it was in the last commit: + +```shell +git restore config.json +``` + +If you added a file too early, i.e. the file is in **staging**: + +```shell +git restore --staged secret_keys.env +``` + +### Git Reset + +This is for time traveling in your tree and has three modes: soft, mixed, and hard. + +#### Oops, I made a typo in the commit (soft) + +You committed your work, but realized you forgot to include one file, or you messed up the commit message. + +```shell +# The commit is gone, but the files are currently green (staged). +# You can fix the file/message and run git commit again. +git reset --soft HEAD~1 +``` + +#### I want to split a big commit into two smaller ones (--mixed) + +This is the default mode when running `git reset` + +```shell +# The commit is gone. The files are there but red (unstaged). +# You can now git add file A, commit it, then git add file B, +# and commit that separately. +git reset HEAD~1 +``` + +#### Throwing in the Towel + +You've tried fixing a bug, spent 3 hours on it, and realized everything is borked. You just want to go back to when the code worked. + +```shell +# All work since the last commit is erased +# Yar, there be dragons. + +git reset --hard HEAD~1 +``` + +## Other Git Topics + +### Rebase vs Merge + +There are two ways to update a Pull Request (PR) branch with upstream changes: **merging** and **rebasing**. Because Flutter is a large-scale project with automated infrastructure, specific assumptions are made regarding a PR's history, diffing, baseline compatibility, and engine artifacts. Flutter runs best with a rebase workflow. + +**Technical reasoning:** + +* `git merge master` (*discouraged*): Creates a non-linear history by generating a new "merge commit" at the tip of your branch. While a merge does shift the local `git merge-base` forward to include the latest `master` commits, it introduces a diamond-shaped commit graph. Automated tooling or metrics scripts checking chronological commit age or topological order can misinterpret the branch history, leading to flaky CI/CD evaluations. +* `git rebase upstream/master` (*encouraged*): Rewrites your branch history by picking up your unique commits and planting them directly on top of the latest `master` commit. This maintains a perfectly **linear history**. The base of your branch becomes the absolute tip of `master`, ensuring that content hashes, tests, and build artifacts are validated against a clean codebase rather than a mixed history. + +**Social reasoning:** + +* **Merge**: If you merge `master` into your branch multiple times over a long development cycle, your PR history becomes cluttered with "Merge branch 'master' into..." commits. This noise obscures your actual work, making it difficult for maintainers to review your specific changes. +* **Rebase**: It presents a clean, chronological story: *"Here is my work, applied directly to the latest codebase."* It signals to the reviewer that you have verified your code works cleanly alongside the most recent upstream changes. + +**Conflict Reasoning:** +Given the high velocity of the Flutter repository, conflicts with `master` are common. + +* **With a merge**, you resolve conflicts inside a noisy, standalone "merge commit." +* **With a rebase**, you resolve conflicts **within your own individual commits**. This keeps the final code clean and ensures your discrete commits remain atomic and functional if they ever need to be cherry-picked later. + +**Force Pushing Safely** + +Because a `rebase` rewrites your branch's commit **history**, a standard `git push` will be rejected by GitHub. You must force push. + +**Never use** `git push -f`. Instead, always use a lease. This instructs Git to check if anyone else has pushed to your remote branch since you last fetched, preventing you from accidentally overwriting a coworker's or maintainer's work. + +```shell +git push --force-with-lease +``` + +> [!TIP] +> Always git rebase on `upstream/master` on Flutter PRs. + +### Interactive Rebase Workflow + +Follow this sequence to cleanly update your feature branch and squash minor commits before requesting a review: + +```shell +# Update your upstream master references and clean up deleted branches +git fetch upstream --tags --prune + +# Or optionally fetch everything. Will take longer. +git fetch --all --tags --prune + +# Start an interactive rebase on top of the fresh upstream master +git rebase -i upstream/master +``` + +**What this does:** + +1. Opens your preferred editor with a list of your commits. +2. Allows you to `squash` (combine commits) or `fixup` (combine commits and discard the message). Use this to merge "typo fix" or "wip" commits into meaningful units of work. +3. Re-applies your newly cleaned, atomic commits sequentially directly on top of the latest upstream master + +### Stacked PRs + +What's better than one giant PR that might be expensive to review? Multiple small PRs that build on top of each other - each building successfully on their own. GitHub doesn't properly support / render stacked PRs like GitLab / Graphite / others; but we can still achieve a happy path. + +Let's say you have broken down a feature into two smaller ones, `feature-1a` and `feature-1b`. + +**Base-feature** +Merge-Base: `master` +Head: `feature-1a` +GitHub UI: Shows only changes in `feature-1a` + +**Stacked PR** +Merge-Base: `feature-1a` +Head: `feature-1b` +GitHub UI: Shows changes in `feature-1b` - **only for branches on flutter/flutter** +Clean-diff: https://github.com//flutter/compare/... + +```shell +# 1. Start on latest HEAD +git worktree add feature-1a + +# 2. Create Feature 1 +cd feature-1a + +# ... work, test, commit ... + +# 3. Create the second part (Stacking on Feature 1) +git switch -c feature-1b + +# ... work, test, commit ... + +# Push both PRs +git push origin feature-1a +git push origin feature-1b + +# Feedback comes in: changes requested in feature-1a; you are currently in feature 1b +# If its simple; you can interactive rebase and edit +git rebase -i --update-refs upstream/master + +# If its not simple; you can checkout feature-1a, do some work, and rebase feature 1b +### + git switch feature-1a + + # hack hack + git add + git commit -m "Fix review comments" + + # Update feature-1b + git switch feature-1b + git rebase feature-1a +### + +# Once done - update the PRs with lease +git push origin feature-1a # assumes you only made commits to feature-1a +git push --force-with-lease origin feature-1b +``` + +If you find yourself rebasing this stacked set of features on top of master; you can set the following to have git automatically move tracked branches: + +```shell +git config --global rebase.updateRefs true +``` + +Example: + +```shell +(Base) A --- B --- C [master] + \ +(Stack 1) D [feature-1a] + \ +(Stack 2) E [feature-1b] (HEAD) <- feature-1b is checked out + +git rebase upstream/master + +(Base) A --- B --- C [master] + \ +(Stack 1) D' [feature-1a] <-- MOVED AUTOMATICALLY! + \ +(Stack 2) E' [feature-1b] (HEAD) +``` + +#### Landing feature-1a / updating feature-1b + +Since Flutter squashes PRs before merging them to master, the history at master is NOT the same as the history in your stacked changes. If you were to rebase feature-1b onto master, you'll hit conflicts. To get around this trap, you simply need to: + +```shell +# 1. Update to get the new squash commit with feature-1a. +git fetch upstream --tags --prune + +# 2. Rebase feature-1b onto master, ignoring the old feature-1a commits +# --onto +git rebase --onto upstream/master feature-1a feature-1b + +# 3. Push the update. +git push --force-with-lease origin feature-1b +``` From e128b431c4c17c84dcc17c5f7492c93d6ff383e1 Mon Sep 17 00:00:00 2001 From: LouiseHsu Date: Fri, 24 Jul 2026 12:29:28 -0700 Subject: [PATCH 045/147] Migrate a11y_assessments to uiscene (#189960) Migrate dev/a11y_assessments/ to uiscene in preperation for accessibility bug bash (and so it can launch w/ xcode 27) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../ios/Flutter/AppFrameworkInfo.plist | 2 -- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../ios/Runner/AppDelegate.swift | 7 ++++++- dev/a11y_assessments/ios/Runner/Info.plist | 21 +++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/dev/a11y_assessments/ios/Flutter/AppFrameworkInfo.plist b/dev/a11y_assessments/ios/Flutter/AppFrameworkInfo.plist index 0d14080090af7..391a902b2bebe 100644 --- a/dev/a11y_assessments/ios/Flutter/AppFrameworkInfo.plist +++ b/dev/a11y_assessments/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 15.0 diff --git a/dev/a11y_assessments/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/a11y_assessments/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5dfe1936..15cada4838e2f 100644 --- a/dev/a11y_assessments/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/dev/a11y_assessments/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/dev/a11y_assessments/ios/Runner/AppDelegate.swift b/dev/a11y_assessments/ios/Runner/AppDelegate.swift index 58f3ea9b92990..68cf615f9babd 100644 --- a/dev/a11y_assessments/ios/Runner/AppDelegate.swift +++ b/dev/a11y_assessments/ios/Runner/AppDelegate.swift @@ -11,7 +11,12 @@ import UIKit _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } + +extension AppDelegate: FlutterImplicitEngineDelegate { + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/dev/a11y_assessments/ios/Runner/Info.plist b/dev/a11y_assessments/ios/Runner/Info.plist index 74b9bb41e71fc..0249c9c6bd697 100644 --- a/dev/a11y_assessments/ios/Runner/Info.plist +++ b/dev/a11y_assessments/ios/Runner/Info.plist @@ -45,5 +45,26 @@ UIApplicationSupportsIndirectInputEvents + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + From bc2162e2701c9688c3f1f3356ca8fbcc9dabe00a Mon Sep 17 00:00:00 2001 From: Reid Baker <1063596+reidbaker@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:00:08 -0400 Subject: [PATCH 046/147] Update contributing guidelines for commit message format (#189922) This style of putting code terms in backticks is implied not stated. https://github.com/flutter/packages/pull/12145/changes#r3625889893 --- docs/ecosystem/contributing/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ecosystem/contributing/README.md b/docs/ecosystem/contributing/README.md index 3fdd223ebd26c..165248a04a567 100644 --- a/docs/ecosystem/contributing/README.md +++ b/docs/ecosystem/contributing/README.md @@ -25,6 +25,7 @@ For consistency, all CHANGELOG entries should follow a common style: - Entries should end with a `.`. - Breaking changes should be introduced with `**BREAKING CHANGE**:`, or `**BREAKING CHANGES**:` if there is a sub-list of changes. - Breaking change notifications should include information about how to migrate. If extensive migration is required, this can be a reference to a longer description elsewhere (usually README.md) rather than inline instructions. +- Code references should be enclosed in backticks. Example: ``` From f62aded0cc8359ce88a955900d2b8f1dbaad039e Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Fri, 24 Jul 2026 13:02:30 -0700 Subject: [PATCH 047/147] fix(ci): quote labels to fix yaml mapping error (#190002) `env:` block requires `string: string` types. --- .github/workflows/scheduled-localization-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scheduled-localization-update.yml b/.github/workflows/scheduled-localization-update.yml index 9e029a3da13bf..ff0bffdea28b5 100644 --- a/.github/workflows/scheduled-localization-update.yml +++ b/.github/workflows/scheduled-localization-update.yml @@ -28,7 +28,7 @@ jobs: GH_REPO: ${{ github.repository }} TITLE: '[Automated Task] Quarterly Localizations Update (flutter_localizations)' ASSIGNEES: QuncCccccc - LABELS: automated task,a: internationalization,team-framework + LABELS: 'automated task,a: internationalization,team-framework' BODY: | ### Quarterly Localization Update From 849e8a226dfc080b621cd8aa9c8b56356c1df1ee Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 18:53:19 -0400 Subject: [PATCH 048/147] Roll Skia from f2f9b52fe1ee to d8d20eef4dbd (21 revisions) (#190011) https://skia.googlesource.com/skia.git/+log/f2f9b52fe1ee..d8d20eef4dbd 2026-07-24 michaelludwig@google.com Handle global resource tracing when SK_DISABLE_TRACING is defined 2026-07-24 jlavrova@google.com Revert "miracleptr: Add RAW_PTR_EXCLUSION for pointers to global/static state" 2026-07-24 arthursonzogni@chromium.org miracleptr: Add RAW_PTR_EXCLUSION for pointers to global/static state 2026-07-24 borenet@google.com Fix README.chromium for skcms 2026-07-24 kjlubick@google.com Revert "miracleptr: Add RAW_PTR_EXCLUSION for pointers in unions" 2026-07-24 kjlubick@google.com Revert "miracleptr: Add RAW_PTR_EXCLUSION for pointers used to pack integers" 2026-07-24 alexisdavidc@google.com [SkCapture] Get Captures Working on Viewer 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 808860b25bf7 to 1f2cbadfb9c8 (1 revision) 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll bazel_build from b5d31abb7bc772a69... to 348e064d2951b8209... (1 revision) 2026-07-24 michaelludwig@google.com [ganesh] Check results of internal flushes for internalWritePixels success 2026-07-24 michaelludwig@google.com Move GlobalResourceStats::Singleton definition into CPP file 2026-07-24 arthursonzogni@chromium.org miracleptr: Add RAW_PTR_EXCLUSION for pointers used to pack integers 2026-07-24 arthursonzogni@chromium.org miracleptr: Add RAW_PTR_EXCLUSION for pointers in unions 2026-07-24 fmalita@google.com Remove SkPath::updateBoundsCache 2026-07-24 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from e2e7fdda0991 to 773cb2ff3ea5 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 8521722b7ebc to e316181208fd (9 revisions) 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 7a6f0a35a522 to d47334d0785d (12 revisions) 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 9b8c24f3b4ee to a56ab87a19a0 (13 revisions) 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from afc9d8707b53 to 808860b25bf7 (4 revisions) 2026-07-24 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index ec0d8a1e5ebfe..b2b2eb53ff5c8 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'f2f9b52fe1eea06f72e0e47922da80cd66973aa7', + 'skia_revision': 'd8d20eef4dbd099aa44f323e39668bd845f16c46', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 9007e14016be88d04eaa57d5b3c7913c47954ea6 Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Fri, 24 Jul 2026 16:09:07 -0700 Subject: [PATCH 049/147] ci: add mac verify binaries workflow (#190010) - Extracts engine change detection logic from `wait-for-engine-build` into a reusable `has-engine-changes` composite action. - Introduces a new `mac-verify-binaries` GitHub Actions workflow. - Fixes an empty string check for `PUSH_BEFORE_SHA` in the `wait-for-engine-build` action. fixes: https://github.com/flutter/flutter/issues/189995 --- .github/actions/has-engine-changes/action.yml | 46 ++++++++++++++++ .../actions/wait-for-engine-build/action.yml | 34 ++---------- .github/workflows/mac-verify-binaries.yml | 54 +++++++++++++++++++ 3 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 .github/actions/has-engine-changes/action.yml create mode 100644 .github/workflows/mac-verify-binaries.yml diff --git a/.github/actions/has-engine-changes/action.yml b/.github/actions/has-engine-changes/action.yml new file mode 100644 index 0000000000000..bbf5f1e465a9d --- /dev/null +++ b/.github/actions/has-engine-changes/action.yml @@ -0,0 +1,46 @@ +name: 'Has Engine Changes' +description: 'Checks if there are any engine changes in the current branch' +outputs: + changed: + description: "Returns 'true' if there are engine changes, 'false' otherwise" + value: ${{ steps.check.outputs.changed }} +runs: + using: "composite" + steps: + - name: Check Engine Changes + id: check + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + run: | + EMPTY_SHA="0000000000000000000000000000000000000000" + if [ "$EVENT_NAME" = "pull_request" ]; then + BASE_REF="$PR_BASE_SHA" + elif [ "$EVENT_NAME" = "merge_group" ]; then + BASE_REF="$MERGE_GROUP_BASE_SHA" + elif [ "$EVENT_NAME" = "push" ] && [ -n "$PUSH_BEFORE_SHA" ] && [ "$PUSH_BEFORE_SHA" != "$EMPTY_SHA" ]; then + BASE_REF="$PUSH_BEFORE_SHA" + else + if git show-ref --verify --quiet refs/remotes/upstream/master; then + BASE_REF="upstream/master" + else + BASE_REF="origin/master" + fi + fi + + echo "Comparing against base SHA: $BASE_REF" + + CHANGED_FILES=$(.github/scripts/git_files_changed.sh "$BASE_REF") + echo "Changed files in this branch:" + echo "$CHANGED_FILES" + + if [ "$(printf '%s\n' "$CHANGED_FILES" | .github/scripts/did_engine_change.sh)" = "true" ]; then + echo "Engine files changed." + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "No engine files changed." + echo "changed=false" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/actions/wait-for-engine-build/action.yml b/.github/actions/wait-for-engine-build/action.yml index b2f4ca0b1a70b..b422fde685f77 100644 --- a/.github/actions/wait-for-engine-build/action.yml +++ b/.github/actions/wait-for-engine-build/action.yml @@ -17,40 +17,16 @@ runs: steps: - name: Check Engine Changes id: check_engine + uses: ./.github/actions/has-engine-changes + + - name: Log wait requirement + if: steps.check_engine.outputs.changed == 'true' shell: bash env: - EVENT_NAME: ${{ github.event_name }} - PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} - MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }} - PUSH_BEFORE_SHA: ${{ github.event.before }} INPUTS_CHECK_NAME: ${{ inputs.check-name }} INPUTS_CHECK_REGEXP: ${{ inputs.check-regexp }} run: | - # 1. Determine local base tracking SHA - if [ "$EVENT_NAME" = "pull_request" ]; then - BASE_REF="$PR_BASE_SHA" - elif [ "$EVENT_NAME" = "merge_group" ]; then - BASE_REF="$MERGE_GROUP_BASE_SHA" - elif [ "$EVENT_NAME" = "push" ] && [ "$PUSH_BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then - BASE_REF="$PUSH_BEFORE_SHA" - else - BASE_REF="origin/master" - fi - - echo "Comparing against base SHA: $BASE_REF" - - # 2. Get the changed files using the helper script - CHANGED_FILES=$(.github/scripts/git_files_changed.sh "$BASE_REF") - echo "Changed files in this branch:" - echo "$CHANGED_FILES" - - # 3. Check if any file matches engine pattern using the helper script: - if [ "$(printf '%s\n' "$CHANGED_FILES" | .github/scripts/did_engine_change.sh)" = "true" ]; then - echo "Engine file changed - forced to wait for check-name: '${INPUTS_CHECK_NAME}', check-regexp: '${INPUTS_CHECK_REGEXP}'" - echo "changed=true" >> "$GITHUB_OUTPUT" - else - echo "changed=false" >> "$GITHUB_OUTPUT" - fi + echo "Engine file changed - forced to wait for check-name: '${INPUTS_CHECK_NAME}', check-regexp: '${INPUTS_CHECK_REGEXP}'" # Note: Use the Cocoon wait-for-tests action to poll the Cocoon API. - name: Wait for test check-in diff --git a/.github/workflows/mac-verify-binaries.yml b/.github/workflows/mac-verify-binaries.yml new file mode 100644 index 0000000000000..799ddd43dfdd8 --- /dev/null +++ b/.github/workflows/mac-verify-binaries.yml @@ -0,0 +1,54 @@ +name: Mac Verify Binaries + +on: + pull_request: + branches: [master] + merge_group: + branches: [master] + +jobs: + check-engine-changes: + permissions: + contents: read + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.check.outputs.changed }} + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Check Engine Changes + id: check + uses: ./.github/actions/has-engine-changes + + Mac_arm64_verify_binaries: + needs: check-engine-changes + if: needs.check-engine-changes.outputs.changed == 'true' + permissions: + contents: read + runs-on: macos-latest + timeout-minutes: 130 + + steps: + - name: Checkout code + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Wait for Engine Build + uses: ./.github/actions/wait-for-engine-build + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + check-name: 'Mac mac_host_engine' + + - uses: ./.github/actions/composite-flutter-setup + + - name: Run verify_binaries_pre_codesigned + run: | + SHARD=verify_binaries_pre_codesigned LUCI_CI=true dart --enable-asserts dev/bots/test.dart From 5a599f2439883f71157d8986341c00c06db74355 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 24 Jul 2026 19:23:06 -0400 Subject: [PATCH 050/147] Roll Dart SDK from 153abd9a8296 to 28e63ac22d8d (2 revisions) (#190013) https://dart.googlesource.com/sdk.git/+log/153abd9a8296..28e63ac22d8d 2026-07-24 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-65.0.dev 2026-07-24 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-64.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jsimmons@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index b2b2eb53ff5c8..0133a17588e3d 100644 --- a/DEPS +++ b/DEPS @@ -56,12 +56,12 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '153abd9a8296ba4bb077c96267974e8540f544e8', + 'dart_revision': '28e63ac22d8dbb455fc7d615c2f7c2e1513f930a', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': '9926156a583cec3d22d521232b31c70fa9a87dc1', - 'dart_boringssl_rev': '922c15f36cc75db5af33c46f9ea8934553fb808e', + 'dart_boringssl_rev': 'e06e0ab4dead084b9269c8646fbdbe951cd3a21b', 'dart_core_rev': 'be0b1531c445a185d3e93887b8d0355fc766c314', 'dart_devtools_rev': '12d595649f189f1896722623f72599077f476848', 'dart_ecosystem_rev': '848b3bf3b757d2e9ae4d60030eeed5756c87783f', From a6ab6a5c3a458233cab98343834180e94e7f430d Mon Sep 17 00:00:00 2001 From: jesswrd Date: Fri, 24 Jul 2026 16:44:09 -0700 Subject: [PATCH 051/147] Bumped Engine AGP Dependencies to 9.1.0 for 3.50 (#189417) Bumped the Engine AGP dependencies to 9.1.0 for Flutter 3.50. This only affects engine contributors so it will not be CP'ed. Partially Addresses https://github.com/flutter/flutter/issues/189232 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- DEPS | 2 +- engine/src/flutter/shell/platform/android/build.gradle | 2 +- .../src/flutter/shell/platform/android/test_runner/build.gradle | 2 +- .../flutter/tools/cipd/android_embedding_bundle/build.gradle | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index 0133a17588e3d..f7c7ab762d803 100644 --- a/DEPS +++ b/DEPS @@ -597,7 +597,7 @@ deps = { { # See tools/gradle/README.md for update instructions. # Version here means the CIPD tag. - 'version': 'version:8.11.1', + 'version': 'version:9.3.1', 'package': 'flutter/gradle' } ], diff --git a/engine/src/flutter/shell/platform/android/build.gradle b/engine/src/flutter/shell/platform/android/build.gradle index 9a2fe44cf0dbe..a49169463efbd 100644 --- a/engine/src/flutter/shell/platform/android/build.gradle +++ b/engine/src/flutter/shell/platform/android/build.gradle @@ -10,7 +10,7 @@ buildscript { dependencies { // Consult the Android team before bumping. This is only used for IDE support, so // it does not need to be bumped as part of most repo-wide upgrades. - classpath "com.android.tools.build:gradle:8.9.1" + classpath "com.android.tools.build:gradle:9.1.0" } } diff --git a/engine/src/flutter/shell/platform/android/test_runner/build.gradle b/engine/src/flutter/shell/platform/android/test_runner/build.gradle index b4f73c0c866db..5edbbb20ed989 100644 --- a/engine/src/flutter/shell/platform/android/test_runner/build.gradle +++ b/engine/src/flutter/shell/platform/android/test_runner/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.9.1" + classpath "com.android.tools.build:gradle:9.1.0" } } diff --git a/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle b/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle index b9cebf621b236..af18220246034 100644 --- a/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle +++ b/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle @@ -13,7 +13,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.11.1" + classpath "com.android.tools.build:gradle:9.1.0" } } From a50ad51fed3e9001ce6b9a2c9be945a9af139133 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Fri, 24 Jul 2026 18:02:40 -0700 Subject: [PATCH 052/147] [Impeller] Add 2D texture array support (kTexture2DArray) (#188778) Part of flutter/flutter#188776. Adds a 2D texture array texture type to Impeller (`TextureType::kTexture2DArray`, sampled as `sampler2DArray`), with the layer count carried by a new `TextureDescriptor::array_layer_count`. The existing cube map is the closest analog (it is already a layered texture), so each backend follows the same path, except the layer count is explicit rather than fixed at 6. Backends: - Metal maps the type to `MTLTextureType2DArray` and sets `arrayLength`. - Vulkan maps it to a 2D-array image view (`e2DArray`) with the descriptor's layer count. - OpenGL ES allocates the array with `glTexImage3D` and fills layers with `glTexSubImage3D`, gated on a new `CapabilitiesGLES::SupportsTextureArray` that is true on ES 3.0+ (and desktop GL 3.0+) and false on ES 2.0, where there is no array-texture equivalent so callers fall back to a texture atlas. Per-layer upload already worked through the existing `slice` parameter on Metal and Vulkan. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [C++, Objective-C, Java style guides]: https://github.com/flutter/flutter/blob/main/engine/src/flutter/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/blob/main/docs/engine/testing/Testing-the-engine.md [CLA]: https://cla.developers.google.com/ --- engine/src/flutter/impeller/core/BUILD.gn | 4 + engine/src/flutter/impeller/core/allocator.cc | 5 + .../impeller/core/allocator_unittests.cc | 27 ++++ engine/src/flutter/impeller/core/formats.h | 8 ++ engine/src/flutter/impeller/core/texture.cc | 2 + engine/src/flutter/impeller/core/texture.h | 7 +- .../impeller/core/texture_descriptor.cc | 25 ++++ .../impeller/core/texture_descriptor.h | 21 +-- .../backend/gles/blit_command_gles.cc | 6 + .../backend/gles/capabilities_gles.cc | 15 ++ .../renderer/backend/gles/capabilities_gles.h | 8 ++ .../renderer/backend/gles/formats_gles.h | 4 + .../renderer/backend/gles/proc_table_gles.cc | 34 ++++- .../renderer/backend/gles/proc_table_gles.h | 12 ++ .../gles/test/capabilities_unittests.cc | 38 +++++ .../gles/test/texture_gles_unittests.cc | 35 +++++ .../renderer/backend/gles/texture_gles.cc | 134 ++++++++++++++++-- .../renderer/backend/gles/texture_gles.h | 8 +- .../renderer/backend/metal/formats_mtl.h | 2 + .../renderer/backend/metal/formats_mtl.mm | 3 + .../renderer/backend/vulkan/allocator_vk.cc | 8 +- .../renderer/backend/vulkan/formats_vk.h | 10 +- .../swapchain/khr/khr_swapchain_image_vk.cc | 2 +- .../backend/vulkan/texture_source_vk.cc | 2 +- .../impeller/renderer/renderer_unittests.cc | 33 +++++ 25 files changed, 418 insertions(+), 35 deletions(-) diff --git a/engine/src/flutter/impeller/core/BUILD.gn b/engine/src/flutter/impeller/core/BUILD.gn index c47f5512f0c6f..ce414e2e6c0dc 100644 --- a/engine/src/flutter/impeller/core/BUILD.gn +++ b/engine/src/flutter/impeller/core/BUILD.gn @@ -43,10 +43,13 @@ impeller_component("core") { "vertex_buffer.h", ] + public_deps = [ "//third_party/abseil-cpp/absl/status" ] + deps = [ "../base", "../geometry", "//flutter/fml", + "//third_party/abseil-cpp/absl/strings", ] } @@ -65,5 +68,6 @@ impeller_component("allocator_unittests") { "../geometry", "//flutter/impeller/renderer/testing:mocks", "//flutter/testing:testing_lib", + "//third_party/abseil-cpp/absl/strings", ] } diff --git a/engine/src/flutter/impeller/core/allocator.cc b/engine/src/flutter/impeller/core/allocator.cc index 9f2a28f8995f1..6b6018f11f20a 100644 --- a/engine/src/flutter/impeller/core/allocator.cc +++ b/engine/src/flutter/impeller/core/allocator.cc @@ -48,6 +48,11 @@ std::shared_ptr Allocator::CreateBuffer( std::shared_ptr Allocator::CreateTexture(const TextureDescriptor& desc, bool threadsafe) { + if (const absl::Status status = desc.Validate(); !status.ok()) { + VALIDATION_LOG << "The texture descriptor is invalid. " << status.message(); + return nullptr; + } + const auto max_size = GetMaxTextureSizeSupported(); if (desc.size.width > max_size.width || desc.size.height > max_size.height) { VALIDATION_LOG << "Requested texture size " << desc.size diff --git a/engine/src/flutter/impeller/core/allocator_unittests.cc b/engine/src/flutter/impeller/core/allocator_unittests.cc index 3566b164fbd27..5d02c1c320acc 100644 --- a/engine/src/flutter/impeller/core/allocator_unittests.cc +++ b/engine/src/flutter/impeller/core/allocator_unittests.cc @@ -9,6 +9,7 @@ #include "impeller/core/texture_descriptor.h" #include "impeller/geometry/size.h" #include "impeller/renderer/testing/mocks.h" +#include "third_party/abseil-cpp/absl/strings/match.h" namespace impeller { namespace testing { @@ -77,6 +78,32 @@ TEST(AllocatorTest, TextureDescriptorCompatibility) { ASSERT_EQ(desc_a, desc_b); ASSERT_NE(desc_a, desc_c); } + // Array layer count. + { + TextureDescriptor desc_a = {.type = TextureType::kTexture2DArray, + .array_layer_count = 4}; + TextureDescriptor desc_b = {.type = TextureType::kTexture2DArray, + .array_layer_count = 4}; + TextureDescriptor desc_c = {.type = TextureType::kTexture2DArray, + .array_layer_count = 8}; + + EXPECT_EQ(desc_a, desc_b); + EXPECT_NE(desc_a, desc_c); + } +} + +TEST(AllocatorTest, TextureDescriptorArrayValidity) { + // A 2D array descriptor is valid with a layer count and invalid with zero + // layers. + TextureDescriptor desc = {.type = TextureType::kTexture2DArray, + .format = PixelFormat::kR8G8B8A8UNormInt, + .size = ISize(16, 16), + .array_layer_count = 4}; + EXPECT_TRUE(desc.IsValid()); + + desc.array_layer_count = 0; + EXPECT_FALSE(desc.IsValid()); + EXPECT_TRUE(absl::StrContains(desc.Validate().message(), "layer")); } TEST(AllocatorTest, RangeTest) { diff --git a/engine/src/flutter/impeller/core/formats.h b/engine/src/flutter/impeller/core/formats.h index b2d1520191aba..93b157b402d8a 100644 --- a/engine/src/flutter/impeller/core/formats.h +++ b/engine/src/flutter/impeller/core/formats.h @@ -452,6 +452,11 @@ enum class TextureType { kTexture2DMultisample, kTextureCube, kTextureExternalOES, + // A 2D texture with multiple layers, sampled as `sampler2DArray`. The layer + // count is carried by `TextureDescriptor::array_layer_count`. Kept last so + // the integer values of the existing types (mirrored by Flutter GPU) are + // unchanged. + kTexture2DArray, }; constexpr const char* TextureTypeToString(TextureType type) { @@ -464,6 +469,8 @@ constexpr const char* TextureTypeToString(TextureType type) { return "TextureCube"; case TextureType::kTextureExternalOES: return "TextureExternalOES"; + case TextureType::kTexture2DArray: + return "Texture2DArray"; } FML_UNREACHABLE(); } @@ -473,6 +480,7 @@ constexpr bool IsMultisampleCapable(TextureType type) { case TextureType::kTexture2D: case TextureType::kTextureCube: case TextureType::kTextureExternalOES: + case TextureType::kTexture2DArray: return false; case TextureType::kTexture2DMultisample: return true; diff --git a/engine/src/flutter/impeller/core/texture.cc b/engine/src/flutter/impeller/core/texture.cc index b12845db6a027..b427a1f41bf09 100644 --- a/engine/src/flutter/impeller/core/texture.cc +++ b/engine/src/flutter/impeller/core/texture.cc @@ -64,6 +64,8 @@ bool Texture::IsSliceValid(size_t slice) const { return slice == 0; case TextureType::kTextureCube: return slice <= 5; + case TextureType::kTexture2DArray: + return slice < static_cast(desc_.array_layer_count); } FML_UNREACHABLE(); } diff --git a/engine/src/flutter/impeller/core/texture.h b/engine/src/flutter/impeller/core/texture.h index 7f1dd7ad7222d..1f82695784373 100644 --- a/engine/src/flutter/impeller/core/texture.h +++ b/engine/src/flutter/impeller/core/texture.h @@ -55,6 +55,11 @@ class Texture { /// modified and the mipmaps hasn't been regenerated. bool NeedsMipmapGeneration() const; + /// Returns true if `slice` addresses a valid layer for this texture's type + /// (0 for 2D textures, 0-5 for cube maps, and below the descriptor's + /// `array_layer_count` for 2D array textures). + bool IsSliceValid(size_t slice) const; + protected: explicit Texture(TextureDescriptor desc); @@ -72,8 +77,6 @@ class Texture { const TextureDescriptor desc_; bool is_opaque_ = false; - bool IsSliceValid(size_t slice) const; - Texture(const Texture&) = delete; Texture& operator=(const Texture&) = delete; diff --git a/engine/src/flutter/impeller/core/texture_descriptor.cc b/engine/src/flutter/impeller/core/texture_descriptor.cc index e55c7ada7068e..4d17ce124f76c 100644 --- a/engine/src/flutter/impeller/core/texture_descriptor.cc +++ b/engine/src/flutter/impeller/core/texture_descriptor.cc @@ -6,8 +6,33 @@ #include +#include "third_party/abseil-cpp/absl/strings/str_cat.h" + namespace impeller { +absl::Status TextureDescriptor::Validate() const { + if (format == PixelFormat::kUnknown) { + return absl::InvalidArgumentError("A pixel format must be specified."); + } + if (size.IsEmpty()) { + return absl::InvalidArgumentError(absl::StrCat( + "The size ", size.width, "x", size.height, " must be nonempty.")); + } + if (mip_count < 1u) { + return absl::InvalidArgumentError("The mip count must be at least one."); + } + if (type == TextureType::kTexture2DArray && array_layer_count < 1u) { + return absl::InvalidArgumentError( + "A 2D array texture must have at least one layer."); + } + if (!SamplingOptionsAreValid()) { + return absl::InvalidArgumentError(absl::StrCat( + "The sample count ", static_cast(sample_count), + " is not valid for the texture type ", TextureTypeToString(type), ".")); + } + return absl::OkStatus(); +} + std::string TextureDescriptorToString(const TextureDescriptor& desc) { std::stringstream stream; stream << "StorageMode=" << StorageModeToString(desc.storage_mode) << ","; diff --git a/engine/src/flutter/impeller/core/texture_descriptor.h b/engine/src/flutter/impeller/core/texture_descriptor.h index e6f48c7b996a6..15dd5bb3e670b 100644 --- a/engine/src/flutter/impeller/core/texture_descriptor.h +++ b/engine/src/flutter/impeller/core/texture_descriptor.h @@ -8,6 +8,7 @@ #include #include "impeller/core/formats.h" #include "impeller/geometry/size.h" +#include "third_party/abseil-cpp/absl/status/status.h" namespace impeller { @@ -44,6 +45,9 @@ struct TextureDescriptor { TextureUsageMask usage = TextureUsage::kShaderRead; SampleCount sample_count = SampleCount::kCount1; CompressionType compression_type = CompressionType::kLossless; + /// The number of layers in a `kTexture2DArray`. Ignored by other texture + /// types (a cube map implies 6 layers from its type). + uint16_t array_layer_count = 1u; /// @brief The number of bytes required to store an image of the given texel /// dimensions in this format. Block-compressed formats round the @@ -53,14 +57,14 @@ struct TextureDescriptor { return BytesForTextureRegion(format, width, height); } - constexpr size_t GetByteSizeOfBaseMipLevel() const { + size_t GetByteSizeOfBaseMipLevel() const { if (!IsValid()) { return 0u; } return GetByteSizeForDimensions(size.width, size.height); } - constexpr size_t GetByteSizeOfAllMipLevels() const { + size_t GetByteSizeOfAllMipLevels() const { if (!IsValid()) { return 0u; } @@ -75,7 +79,7 @@ struct TextureDescriptor { return result; } - constexpr size_t GetBytesPerRow() const { + size_t GetBytesPerRow() const { if (!IsValid()) { return 0u; } @@ -89,12 +93,11 @@ struct TextureDescriptor { constexpr bool operator==(const TextureDescriptor& other) const = default; - constexpr bool IsValid() const { - return format != PixelFormat::kUnknown && // - !size.IsEmpty() && // - mip_count >= 1u && // - SamplingOptionsAreValid(); - } + /// @brief Returns why this descriptor cannot describe a real texture, or + /// OkStatus when it can. + absl::Status Validate() const; + + bool IsValid() const { return Validate().ok(); } }; std::string TextureDescriptorToString(const TextureDescriptor& desc); diff --git a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc index 8bd7d1b377d06..80e415a7e41fb 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/blit_command_gles.cc @@ -181,6 +181,12 @@ bool BlitCopyBufferToTextureCommandGLES::Encode( texture_type = GL_TEXTURE_CUBE_MAP; texture_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice; break; + case TextureType::kTexture2DArray: + // TODO(bdero): Upload into 2D array layers via glTexSubImage3D from the + // blit path. Direct uploads via Texture::SetContents are supported. + VALIDATION_LOG << "Blitting into a 2D array texture is not yet supported " + "on the OpenGLES backend."; + return false; case TextureType::kTextureExternalOES: texture_type = GL_TEXTURE_EXTERNAL_OES; texture_target = GL_TEXTURE_EXTERNAL_OES; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc index 21e45f47a09d9..cf7d9b25ca8b8 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.cc @@ -207,6 +207,17 @@ CapabilitiesGLES::CapabilitiesGLES(const ProcTableGLES& gl) { desc->GetGlVersion().major_version >= 3 || desc->HasExtension(kAppleTextureMaxLevelExt); + // 2D array textures (GL_TEXTURE_2D_ARRAY, sampled as sampler2DArray) need the + // 3D texture upload entry points. These are core on desktop GL 3.0 and + // OpenGL ES 3.0, and reachable below them via GL_EXT_texture_array (desktop + // GL 2.x) or GL_NV_texture_array (OpenGL ES 2.0). Gate on the resolved procs + // rather than the version so a context that advertises an extension but does + // not actually provide the entry points is treated as unsupported, and so + // ES 2.0 devices that do expose them are supported. + supports_texture_array_ = gl.TexImage3D.IsAvailable() && + gl.TexSubImage3D.IsAvailable() && + gl.CompressedTexSubImage3D.IsAvailable(); + // Anisotropic filtering is not part of any core GL or GLES version; it is // always gated on GL_EXT_texture_filter_anisotropic. The query and the // texture parameter are applied with core ES 2.0 entry points (GetFloatv @@ -238,6 +249,10 @@ bool CapabilitiesGLES::SupportsTextureMaxLevel() const { return supports_texture_max_level_; } +bool CapabilitiesGLES::SupportsTextureArray() const { + return supports_texture_array_; +} + size_t CapabilitiesGLES::GetMaxTextureUnits(ShaderStage stage) const { switch (stage) { case ShaderStage::kVertex: diff --git a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.h index 2ddde81473707..f2f59699a0d6f 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/capabilities_gles.h @@ -92,6 +92,13 @@ class CapabilitiesGLES final /// chain cannot be made mipmap complete and samples as black. bool SupportsTextureMaxLevel() const; + /// @brief Whether 2D array textures (`GL_TEXTURE_2D_ARRAY`, `sampler2DArray`) + /// are available. Core on desktop GL 3.0+ and OpenGL ES 3.0+, and also + /// available below them through GL_EXT_texture_array (desktop GL 2.x) + /// or GL_NV_texture_array (OpenGL ES 2.0). When absent, callers must + /// fall back to a texture atlas. + bool SupportsTextureArray() const; + // |Capabilities| bool SupportsOffscreenMSAA() const override; @@ -173,6 +180,7 @@ class CapabilitiesGLES final bool supports_implicit_msaa_ = false; bool supports_32bit_primitive_indices_ = false; bool supports_texture_max_level_ = false; + bool supports_texture_array_ = false; bool is_angle_ = false; bool is_es_ = false; bool supports_texture_compression_bc_ = false; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/formats_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/formats_gles.h index 1d127ed99dde6..4810dd631cd8d 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/formats_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/formats_gles.h @@ -195,6 +195,8 @@ constexpr GLenum ToTextureType(TextureType type) { return GL_TEXTURE_2D_MULTISAMPLE; case TextureType::kTextureCube: return GL_TEXTURE_CUBE_MAP; + case TextureType::kTexture2DArray: + return GL_TEXTURE_2D_ARRAY; case TextureType::kTextureExternalOES: return GL_TEXTURE_EXTERNAL_OES; } @@ -209,6 +211,8 @@ constexpr std::optional ToTextureTarget(TextureType type) { return GL_TEXTURE_2D; case TextureType::kTextureCube: return GL_TEXTURE_CUBE_MAP; + case TextureType::kTexture2DArray: + return GL_TEXTURE_2D_ARRAY; case TextureType::kTextureExternalOES: return GL_TEXTURE_EXTERNAL_OES; } diff --git a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc index 16fd3a3748df7..5630238cb8dd2 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.cc @@ -72,6 +72,20 @@ ProcTableGLES::Resolver WrappedResolver( }; } +// Resolves `gl_name` and installs it as `proc`'s implementation. Used to alias +// extension entry points that share a core proc's signature under a different +// name (e.g. the *NV-suffixed GL_NV_texture_array procs). +template +static void BindProcAlias(Proc& proc, + const Resolver& resolver, + const ErrorFn& error_fn, + const char* gl_name) { + if (auto fn_ptr = resolver(gl_name)) { + proc.function = reinterpret_cast(fn_ptr); + proc.error_fn = error_fn; + } +} + ProcTableGLES::ProcTableGLES( // NOLINT(google-readability-function-size) Resolver resolver) { // The reason this constructor has anywhere near enough code to tip off @@ -122,10 +136,28 @@ ProcTableGLES::ProcTableGLES( // NOLINT(google-readability-function-size) proc_ivar.error_fn = error_fn; \ } - if (description_->GetGlVersion().IsAtLeast(Version(3))) { + const bool supports_gl3 = description_->GetGlVersion().IsAtLeast(Version(3)); + if (supports_gl3) { FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); } + // 2D array textures need the 3D texture entry points. They are core on + // GL/GLES 3.0, exposed on desktop GL 2.x through GL_EXT_texture_array (which + // uses the same entry-point names), and on OpenGL ES 2.0 through + // GL_NV_texture_array (which suffixes them with NV). The NV entry points + // share the core signatures, so they are resolved as aliases into the core + // procs and the rest of the backend can call them without branching. + if (supports_gl3 || description_->HasExtension("GL_EXT_texture_array")) { + FOR_EACH_IMPELLER_TEXTURE_ARRAY_PROC(IMPELLER_PROC); + } else if (description_->HasExtension("GL_NV_texture_array")) { + BindProcAlias(TexImage3D, resolver, error_fn, "glTexImage3DNV"); + BindProcAlias(TexSubImage3D, resolver, error_fn, "glTexSubImage3DNV"); + BindProcAlias(CompressedTexImage3D, resolver, error_fn, + "glCompressedTexImage3DNV"); + BindProcAlias(CompressedTexSubImage3D, resolver, error_fn, + "glCompressedTexSubImage3DNV"); + } + FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC); #undef IMPELLER_PROC diff --git a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h index e9e52f36db542..a093159c25eee 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/proc_table_gles.h @@ -271,6 +271,17 @@ void(glDepthRange)(GLdouble n, GLdouble f); PROC(BlitFramebuffer); \ PROC(InvalidateFramebuffer); +// 3D texture entry points, used by 2D array textures. These are core on +// GL/GLES 3.0 and also reachable below it: desktop GL 2.x exposes these exact +// entry points through GL_EXT_texture_array, and OpenGL ES 2.0 exposes them +// through GL_NV_texture_array under *NV-suffixed names (resolved as aliases +// into these procs). See ProcTableGLES setup. +#define FOR_EACH_IMPELLER_TEXTURE_ARRAY_PROC(PROC) \ + PROC(TexImage3D); \ + PROC(TexSubImage3D); \ + PROC(CompressedTexImage3D); \ + PROC(CompressedTexSubImage3D); + #define FOR_EACH_IMPELLER_EXT_PROC(PROC) \ PROC(DebugMessageControlKHR); \ PROC(DebugMessageCallbackKHR); \ @@ -319,6 +330,7 @@ class ProcTableGLES { FOR_EACH_IMPELLER_ES_ONLY_PROC(IMPELLER_PROC); FOR_EACH_IMPELLER_DESKTOP_ONLY_PROC(IMPELLER_PROC); FOR_EACH_IMPELLER_GLES3_PROC(IMPELLER_PROC); + FOR_EACH_IMPELLER_TEXTURE_ARRAY_PROC(IMPELLER_PROC); FOR_EACH_IMPELLER_EXT_PROC(IMPELLER_PROC); #undef IMPELLER_PROC diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/capabilities_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/capabilities_unittests.cc index fe5f0f799d131..edb515240abd0 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/test/capabilities_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/capabilities_unittests.cc @@ -89,5 +89,43 @@ TEST(CapabilitiesGLES, MaxSamplerAnisotropyWithExtension) { EXPECT_GE(capabilities->GetMaxSamplerAnisotropy(), 2u); } +TEST(CapabilitiesGLES, SupportsTextureArrayOnES3) { + // 2D array textures are core on OpenGL ES 3.0, no extension needed. + auto mock_gles = MockGLES::Init(std::nullopt, "OpenGL ES 3.0"); + auto capabilities = mock_gles->GetProcTable().GetCapabilities(); + EXPECT_TRUE(capabilities->SupportsTextureArray()); +} + +TEST(CapabilitiesGLES, DoesNotSupportTextureArrayOnES2WithoutExtension) { + auto const extensions = std::vector{"GL_KHR_debug"}; + auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0"); + auto capabilities = mock_gles->GetProcTable().GetCapabilities(); + EXPECT_FALSE(capabilities->SupportsTextureArray()); +} + +TEST(CapabilitiesGLES, SupportsTextureArrayViaNVExtensionOnES2) { + // OpenGL ES 2.0 has no core array textures, but GL_NV_texture_array exposes + // them through *NV-suffixed 3D texture entry points. + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_NV_texture_array", // + }; + auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0"); + auto capabilities = mock_gles->GetProcTable().GetCapabilities(); + EXPECT_TRUE(capabilities->SupportsTextureArray()); +} + +TEST(CapabilitiesGLES, SupportsTextureArrayViaEXTExtension) { + // GL_EXT_texture_array exposes the core-named 3D texture entry points below + // GL 3.0 (desktop GL 2.x). + auto const extensions = std::vector{ + "GL_KHR_debug", // + "GL_EXT_texture_array", // + }; + auto mock_gles = MockGLES::Init(extensions, "OpenGL ES 2.0"); + auto capabilities = mock_gles->GetProcTable().GetCapabilities(); + EXPECT_TRUE(capabilities->SupportsTextureArray()); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc index 2a2090696218b..06adbe9f032c4 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/texture_gles_unittests.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "flutter/impeller/playground/playground_test.h" #include "flutter/impeller/renderer/backend/gles/context_gles.h" #include "flutter/impeller/renderer/backend/gles/texture_gles.h" @@ -148,6 +150,39 @@ TEST_P(TextureGLESTest, Leak) { EXPECT_FALSE(handle.has_value()); } +TEST_P(TextureGLESTest, CanCreateAndUpload2DArrayTexture) { + ContextGLES& context_gles = ContextGLES::Cast(*GetContext()); + if (!context_gles.GetReactor() + ->GetProcTable() + .GetCapabilities() + ->SupportsTextureArray()) { + GTEST_SKIP() << "2D array textures are not supported on this context."; + } + + TextureDescriptor desc; + desc.storage_mode = StorageMode::kHostVisible; + desc.size = {2, 2}; + desc.format = PixelFormat::kR8G8B8A8UNormInt; + desc.type = TextureType::kTexture2DArray; + desc.array_layer_count = 3; + desc.mip_count = 1; + + auto texture = GetContext()->GetResourceAllocator()->CreateTexture(desc); + ASSERT_TRUE(texture); + EXPECT_EQ(static_cast(texture->GetTextureDescriptor().array_layer_count), + 3); + EXPECT_TRUE(texture->IsSliceValid(2)); + EXPECT_FALSE(texture->IsSliceValid(3)); + + // Every layer can be uploaded. + std::vector layer(2u * 2u * 4u, 0xFF); + for (size_t slice = 0; slice < static_cast(desc.array_layer_count); + ++slice) { + EXPECT_TRUE(texture->SetContents(layer.data(), layer.size(), slice)); + } + EXPECT_TRUE(context_gles.GetReactor()->React()); +} + TEST_P(TextureGLESTest, CreatingAndBindingEmptyTexturesDoesNotCrash) { ContextGLES& context_gles = ContextGLES::Cast(*GetContext()); const ProcTableGLES& gl = context_gles.GetReactor()->GetProcTable(); diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc index 4c00888683432..a19756936f464 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.cc @@ -163,6 +163,7 @@ TextureGLES::TextureGLES(std::shared_ptr reactor, if (!GetTextureDescriptor().IsValid()) { return; } + // Ensure the texture doesn't exceed device capabilities. const auto tex_size = GetTextureDescriptor().size; const auto max_size = @@ -261,12 +262,31 @@ bool TextureGLES::OnSetContents(std::shared_ptr mapping, texture_type = GL_TEXTURE_CUBE_MAP; texture_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice; break; + case TextureType::kTexture2DArray: + texture_type = GL_TEXTURE_2D_ARRAY; + texture_target = GL_TEXTURE_2D_ARRAY; + break; case TextureType::kTextureExternalOES: texture_type = GL_TEXTURE_EXTERNAL_OES; texture_target = GL_TEXTURE_EXTERNAL_OES; break; } + // Array textures allocate all layers up front (glTexImage3D); a per-layer + // upload then fills one layer with glTexSubImage3D, so make sure the storage + // exists before uploading. + const bool is_array = tex_descriptor.type == TextureType::kTexture2DArray; + if (is_array) { + // Bail out synchronously on contexts without array support (e.g. ES 2.0). + // The glTexImage3D/glTexSubImage3D procs are null there, so queuing the + // upload would dereference a null proc on the reactor thread. + if (!reactor_->GetProcTable().GetCapabilities()->SupportsTextureArray()) { + VALIDATION_LOG << "2D array textures are not supported on this context."; + return false; + } + InitializeContentsIfNecessary(); + } + std::optional gles_format = ToPixelFormatGLES(tex_descriptor.format, /*supports_bgra=*/ @@ -284,7 +304,9 @@ bool TextureGLES::OnSetContents(std::shared_ptr mapping, size = tex_descriptor.size, // image_size = tex_descriptor.GetByteSizeOfBaseMipLevel(), // texture_type, // - texture_target // + texture_target, // + is_array, // + slice // ](const auto& reactor) { auto gl_handle = reactor.GetGLHandle(handle); if (!gl_handle.has_value()) { @@ -303,7 +325,37 @@ bool TextureGLES::OnSetContents(std::shared_ptr mapping, TRACE_EVENT1("impeller", "TexImage2DUpload", "Bytes", std::to_string(mapping->GetSize()).c_str()); gl.PixelStorei(GL_UNPACK_ALIGNMENT, 1); - if (format.is_compressed) { + if (is_array) { + // Storage for every layer is allocated by the initializer; fill the + // requested layer (slice) of the already-allocated base mip level. + if (format.is_compressed) { + gl.CompressedTexSubImage3D( + /*target=*/texture_target, // + /*level=*/0u, // + /*xoffset=*/0u, // + /*yoffset=*/0u, // + /*zoffset=*/static_cast(slice), // + /*width=*/size.width, // + /*height=*/size.height, // + /*depth=*/1, // + /*format=*/format.internal_format, // + /*image_size=*/image_size, // + /*data=*/tex_data); // + } else { + gl.TexSubImage3D( + /*target=*/texture_target, // + /*level=*/0u, // + /*xoffset=*/0u, // + /*yoffset=*/0u, // + /*zoffset=*/static_cast(slice), // + /*width=*/size.width, // + /*height=*/size.height, // + /*depth=*/1, // + /*format=*/format.external_format, // + /*type=*/format.type, // + /*data=*/tex_data); // + } + } else if (format.is_compressed) { gl.CompressedTexImage2D(texture_target, // target 0u, // LOD level format.internal_format, // internal format @@ -453,6 +505,31 @@ void TextureGLES::InitializeContentsIfNecessary() { ); MarkSliceMipLevelInitialized(face, 0); } + } else if (desc.type == TextureType::kTexture2DArray) { + if (!gl.GetCapabilities()->SupportsTextureArray()) { + VALIDATION_LOG << "2D array textures are not supported on this " + "context."; + return; + } + // Array textures allocate the whole base mip level (all layers) in one + // glTexImage3D call; individual layers are then filled with + // glTexSubImage3D. Non-zero mip levels are allocated lazily. + gl.BindTexture(GL_TEXTURE_2D_ARRAY, handle.value()); + gl.TexImage3D( + /*target=*/GL_TEXTURE_2D_ARRAY, // + /*level=*/0u, // + /*internal_format=*/gles_format->internal_format, // + /*width=*/size.width, // + /*height=*/size.height, // + /*depth=*/desc.array_layer_count, // + /*border=*/0u, // + /*format=*/gles_format->external_format, // + /*type=*/gles_format->type, // + /*data=*/nullptr // + ); + // glTexImage3D allocated every layer of the base level at once, so a + // single entry covers the whole level. + MarkSliceMipLevelInitialized(0, 0); } else { // 2D / multisampled. External-OES textures are always wrapped, so // they returned at the is_wrapped_ check above. Only the base mip @@ -609,6 +686,8 @@ bool TextureGLES::GenerateMipmap() { return false; case TextureType::kTextureCube: break; + case TextureType::kTexture2DArray: + break; case TextureType::kTextureExternalOES: break; } @@ -665,21 +744,50 @@ bool TextureGLES::EnsureSliceMipLevelStorage(size_t slice, size_t mip_level) { return false; } ISize size = GetSize(); + const GLsizei mip_width = + static_cast(std::max(1, size.width >> mip_level)); + const GLsizei mip_height = + static_cast(std::max(1, size.height >> mip_level)); + + if (desc.type == TextureType::kTexture2DArray) { + // glTexImage3D allocates this mip level for every layer at once, so a + // single entry (slice 0) tracks the whole level regardless of which layer + // was requested. Guard on that entry so a request for a non-zero layer + // does not re-allocate and orphan already-uploaded layers. + if (IsSliceMipLevelInitialized(0, mip_level)) { + return true; + } + gl.BindTexture(GL_TEXTURE_2D_ARRAY, handle.value()); + gl.TexImage3D( + /*target=*/GL_TEXTURE_2D_ARRAY, // + /*level=*/static_cast(mip_level), // + /*internal_format=*/gles_format->internal_format, // + /*width=*/mip_width, // + /*height=*/mip_height, // + /*depth=*/desc.array_layer_count, // + /*border=*/0u, // + /*format=*/gles_format->external_format, // + /*type=*/gles_format->type, // + /*data=*/nullptr // + ); + MarkSliceMipLevelInitialized(0, mip_level); + return true; + } + bool is_cube = desc.type == TextureType::kTextureCube; GLenum image_target = is_cube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice : GL_TEXTURE_2D; gl.BindTexture(is_cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D, handle.value()); - gl.TexImage2D(image_target, // target - static_cast(mip_level), // LOD level - gles_format->internal_format, // internal - static_cast( - std::max(1, size.width >> mip_level)), // width - static_cast( - std::max(1, size.height >> mip_level)), // height - 0u, // border - gles_format->external_format, // format - gles_format->type, // type - nullptr // data + gl.TexImage2D( + /*target=*/image_target, // + /*level=*/static_cast(mip_level), // + /*internal_format=*/gles_format->internal_format, // + /*width=*/mip_width, // + /*height=*/mip_height, // + /*border=*/0u, // + /*format=*/gles_format->external_format, // + /*type=*/gles_format->type, // + /*data=*/nullptr // ); MarkSliceMipLevelInitialized(slice, mip_level); return true; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h index c1a52f0946a56..8db5ad868215b 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/texture_gles.h @@ -188,8 +188,12 @@ class TextureGLES final : public Texture, // pipeline) keeps its single base-level allocation, and per-level uploads // only pay for the levels they actually touch. // - // Sized for up to 6 cubemap faces × 16 mip levels (covers a 32k base - // dimension); requested levels above this are simply not tracked. + // One entry per independently-allocated slice: 6 for a cube (each face is a + // separate `glTexImage2D`), 1 otherwise. Array textures also use a single + // entry because `glTexImage3D` allocates every layer of a mip level at once, + // so there is nothing per-layer to track. Each entry covers up to 16 mip + // levels (a 32k base dimension); requested levels above this are simply not + // tracked. static constexpr size_t kMaxTrackedMipLevels = 16; std::array, 6> slice_mip_initialized_ = {}; const bool is_wrapped_; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h index 7c36087dc7edb..a0845438e3a60 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.h @@ -440,6 +440,8 @@ constexpr MTLTextureType ToMTLTextureType(TextureType type) { return MTLTextureType2DMultisample; case TextureType::kTextureCube: return MTLTextureTypeCube; + case TextureType::kTexture2DArray: + return MTLTextureType2DArray; case TextureType::kTextureExternalOES: VALIDATION_LOG << "kTextureExternalOES can not be used with the Metal backend."; diff --git a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm index cb6eee57f543a..c377a5b5f6142 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/formats_mtl.mm @@ -94,6 +94,9 @@ mtl_desc.width = desc.size.width; mtl_desc.height = desc.size.height; mtl_desc.mipmapLevelCount = desc.mip_count; + if (desc.type == TextureType::kTexture2DArray) { + mtl_desc.arrayLength = desc.array_layer_count; + } mtl_desc.usage = MTLTextureUsageUnknown; if (desc.usage & TextureUsage::kUnknown) { mtl_desc.usage |= MTLTextureUsageUnknown; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc index 20313ea2aad52..9d1820aff132b 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/allocator_vk.cc @@ -308,7 +308,7 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { }; image_info.samples = ToVKSampleCount(desc.sample_count); image_info.mipLevels = desc.mip_count; - image_info.arrayLayers = ToArrayLayerCount(desc.type); + image_info.arrayLayers = ToArrayLayerCount(desc); image_info.tiling = vk::ImageTiling::eOptimal; image_info.initialLayout = vk::ImageLayout::eUndefined; image_info.usage = AllocatorVK::ToVKImageUsageFlags( @@ -412,7 +412,7 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { view_info.format = image_info.format; view_info.subresourceRange.aspectMask = ToVKImageAspectFlags(desc.format); view_info.subresourceRange.levelCount = image_info.mipLevels; - view_info.subresourceRange.layerCount = ToArrayLayerCount(desc.type); + view_info.subresourceRange.layerCount = ToArrayLayerCount(desc); // Vulkan does not have an image format that is equivalent to // `MTLPixelFormatA8Unorm`, so we use `R8Unorm` instead. Given that the @@ -439,7 +439,7 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { const bool is_render_target = !!(desc.usage & TextureUsage::kRenderTarget); const uint32_t rt_mip_count = is_render_target ? image_info.mipLevels : 1u; const uint32_t rt_layer_count = - is_render_target ? ToArrayLayerCount(desc.type) : 1u; + is_render_target ? ToArrayLayerCount(desc) : 1u; std::vector rt_image_views; rt_image_views.reserve(rt_mip_count * rt_layer_count); for (uint32_t mip = 0; mip < rt_mip_count; mip++) { @@ -480,7 +480,7 @@ class AllocatedTextureSourceVK final : public TextureSourceVK { if (views.empty()) { return VK_NULL_HANDLE; } - const uint32_t layer_count = ToArrayLayerCount(GetTextureDescriptor().type); + const uint32_t layer_count = ToArrayLayerCount(GetTextureDescriptor()); const size_t index = static_cast(mip_level) * layer_count + array_layer; return index < views.size() ? views[index].get() : views[0].get(); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h index d8fa3721207bd..1c32f7fc6ce97 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/formats_vk.h @@ -12,6 +12,7 @@ #include "impeller/base/validation.h" #include "impeller/core/formats.h" #include "impeller/core/shader_types.h" +#include "impeller/core/texture_descriptor.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "vulkan/vulkan_enums.hpp" @@ -608,11 +609,13 @@ constexpr vk::ImageAspectFlags ToVKImageAspectFlags(PixelFormat format) { FML_UNREACHABLE(); } -constexpr uint32_t ToArrayLayerCount(TextureType type) { - switch (type) { +constexpr uint32_t ToArrayLayerCount(const TextureDescriptor& desc) { + switch (desc.type) { case TextureType::kTexture2D: case TextureType::kTexture2DMultisample: return 1u; + case TextureType::kTexture2DArray: + return desc.array_layer_count; case TextureType::kTextureCube: return 6u; case TextureType::kTextureExternalOES: @@ -627,6 +630,8 @@ constexpr vk::ImageViewType ToVKImageViewType(TextureType type) { case TextureType::kTexture2D: case TextureType::kTexture2DMultisample: return vk::ImageViewType::e2D; + case TextureType::kTexture2DArray: + return vk::ImageViewType::e2DArray; case TextureType::kTextureCube: return vk::ImageViewType::eCube; case TextureType::kTextureExternalOES: @@ -640,6 +645,7 @@ constexpr vk::ImageCreateFlags ToVKImageCreateFlags(TextureType type) { switch (type) { case TextureType::kTexture2D: case TextureType::kTexture2DMultisample: + case TextureType::kTexture2DArray: return {}; case TextureType::kTextureCube: return vk::ImageCreateFlagBits::eCubeCompatible; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc index 613a2e1bcd8af..8b54b61ecff4e 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc @@ -18,7 +18,7 @@ KHRSwapchainImageVK::KHRSwapchainImageVK(TextureDescriptor desc, view_info.subresourceRange.baseMipLevel = 0u; view_info.subresourceRange.baseArrayLayer = 0u; view_info.subresourceRange.levelCount = desc.mip_count; - view_info.subresourceRange.layerCount = ToArrayLayerCount(desc.type); + view_info.subresourceRange.layerCount = ToArrayLayerCount(desc); auto [view_result, view] = device.createImageViewUnique(view_info); if (view_result != vk::Result::eSuccess) { diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc index b8dae55071a40..13f41ef991887 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc @@ -45,7 +45,7 @@ fml::Status TextureSourceVK::SetLayout(const BarrierVK& barrier) const { image_barrier.subresourceRange.levelCount = desc_.mip_count - barrier.base_mip_level; image_barrier.subresourceRange.baseArrayLayer = 0u; - image_barrier.subresourceRange.layerCount = ToArrayLayerCount(desc_.type); + image_barrier.subresourceRange.layerCount = ToArrayLayerCount(desc_); barrier.cmd_buffer.pipelineBarrier(barrier.src_stage, // src stage barrier.dst_stage, // dst stage diff --git a/engine/src/flutter/impeller/renderer/renderer_unittests.cc b/engine/src/flutter/impeller/renderer/renderer_unittests.cc index e0b024b6245a0..4082b2ae2aac7 100644 --- a/engine/src/flutter/impeller/renderer/renderer_unittests.cc +++ b/engine/src/flutter/impeller/renderer/renderer_unittests.cc @@ -1616,6 +1616,39 @@ TEST_P(RendererTest, BindingNullTexturesDoesNotCrash) { EXPECT_FALSE(FS::BindContents2(*pass, nullptr, sampler)); } +// Creating and uploading per-layer contents of a 2D array texture. The GLES +// path requires ES 3.0 and is covered separately in the GLES texture unit +// tests, so this skips the GLES backends to avoid an ES 2.0 failure. +TEST_P(RendererTest, CanCreateAndUpload2DArrayTexture) { + if (GetBackend() == PlaygroundBackend::kOpenGLES || + GetBackend() == PlaygroundBackend::kOpenGLESSDF) { + GTEST_SKIP() << "Covered by the GLES-specific texture array test."; + } + auto context = GetContext(); + ASSERT_TRUE(context); + + TextureDescriptor desc; + desc.storage_mode = StorageMode::kHostVisible; + desc.type = TextureType::kTexture2DArray; + desc.format = PixelFormat::kR8G8B8A8UNormInt; + desc.size = {2, 2}; + desc.array_layer_count = 4; + desc.mip_count = 1; + + auto texture = context->GetResourceAllocator()->CreateTexture(desc); + ASSERT_TRUE(texture); + EXPECT_EQ(static_cast(texture->GetTextureDescriptor().array_layer_count), + 4); + EXPECT_TRUE(texture->IsSliceValid(3)); + EXPECT_FALSE(texture->IsSliceValid(4)); + + std::vector layer(2u * 2u * 4u, 0xFF); + for (size_t slice = 0; slice < static_cast(desc.array_layer_count); + ++slice) { + EXPECT_TRUE(texture->SetContents(layer.data(), layer.size(), slice)); + } +} + // Clears a single cube map face by attaching it as a render target slice. // Rendering to cube faces is portable down to OpenGL ES 2.0, so this runs on // every backend. From 9ef3f4e925ed853678da60390c10342a1d044df4 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 25 Jul 2026 03:24:32 -0400 Subject: [PATCH 053/147] Roll Skia from d8d20eef4dbd to 2c014dc494f1 (1 revision) (#190020) https://skia.googlesource.com/skia.git/+log/d8d20eef4dbd..2c014dc494f1 2026-07-24 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll ANGLE from e316181208fd to 92de898b7a32 (10 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f7c7ab762d803..8b9612fd6dc45 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'd8d20eef4dbd099aa44f323e39668bd845f16c46', + 'skia_revision': '2c014dc494f1d7f0063ab1fd849d9292a0875a65', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From e2921977d4cc8741eb51b85febf95b3f41e643e5 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 25 Jul 2026 05:18:41 -0400 Subject: [PATCH 054/147] Roll Skia from 2c014dc494f1 to 62442d6cf0ec (1 revision) (#190022) https://skia.googlesource.com/skia.git/+log/2c014dc494f1..62442d6cf0ec 2026-07-25 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 1f2cbadfb9c8 to 8e393ad533ce (8 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jlavrova@google.com,jsimmons@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8b9612fd6dc45..5178103b6982e 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '2c014dc494f1d7f0063ab1fd849d9292a0875a65', + 'skia_revision': '62442d6cf0ec21e3e7f1c23b018c619374fa39c7', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 99889603182d2a9bb69504c3befbcda77e6c0c27 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 25 Jul 2026 15:37:20 -0400 Subject: [PATCH 055/147] Roll Fuchsia Linux SDK from Cdb1RGKCqpcz9z4cl... to vpboK5fPPIoFteqRq... (#190028) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jsimmons@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5178103b6982e..0204a1d2d1d2e 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'Cdb1RGKCqpcz9z4clTA81sbqT1NH4GfXtp_SKiYYnQsC' + 'version': 'vpboK5fPPIoFteqRqfWrqsRlNMTI9Bq7YFY_b51Mf5MC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From be9b20458a46e6b87f572d8ed681e3ddae6efd3a Mon Sep 17 00:00:00 2001 From: Andy Wolff Date: Mon, 27 Jul 2026 07:51:21 -0700 Subject: [PATCH 056/147] Android_hardware_smoke_test: clean up golden copy in CI (#189948) In PR https://github.com/flutter/flutter/pull/189390 (part of issue https://github.com/flutter/flutter/issues/187538) we configured some new CI test shards which run the instrumented tests subsequently after the driver tests. As part of that, we had to copy the goldens to the asset directory so they would be bundled into the instrumented test APK. This PR proposes to skip the copy by configuring the skia gold comparator to write directly to the desired directory, via a new optional `localOutputDir` parameter. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../run_android_hardware_smoke_tests.dart | 28 +------ .../test_driver/driver_test.dart | 83 ++++++++++++++----- .../lib/skia_gold.dart | 27 +++++- 3 files changed, 86 insertions(+), 52 deletions(-) diff --git a/dev/bots/suite_runners/run_android_hardware_smoke_tests.dart b/dev/bots/suite_runners/run_android_hardware_smoke_tests.dart index 24056c7aefa23..b667dc6845e73 100644 --- a/dev/bots/suite_runners/run_android_hardware_smoke_tests.dart +++ b/dev/bots/suite_runners/run_android_hardware_smoke_tests.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:io' show Platform; + import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:path/path.dart' as path; @@ -15,18 +16,6 @@ import 'run_android_engine_tests.dart'; String _impellerBackendMetadata({required String value}) => ''; -void _copyDirectory(Directory source, Directory destination) { - destination.createSync(recursive: true); - for (final FileSystemEntity entity in source.listSync(recursive: true)) { - if (entity is File) { - final String relativePath = path.relative(entity.path, from: source.path); - final String destPath = path.join(destination.path, relativePath); - entity.fileSystem.file(destPath).parent.createSync(recursive: true); - entity.copySync(destPath); - } - } -} - void _cleanGoldensDirectory(Directory directory) { if (!directory.existsSync()) { return; @@ -64,9 +53,6 @@ Future runAndroidHardwareSmokeTests({ final Directory destinationDir = const LocalFileSystem().directory( path.join(testDir, 'test_driver', 'goldens'), ); - final Directory sourceDir = const LocalFileSystem().directory( - path.join(testDir, 'android_hardware_smoke_test.${backend.name}.goldens'), - ); try { // Replace whatever the current backend is with the specified backend. @@ -93,13 +79,6 @@ Future runAndroidHardwareSmokeTests({ ], workingDirectory: testDir); if (runInstrumented) { - // 2. Copy the generated goldens to the assets directory so they get packaged with the APK. - // In CI, the Skia Gold comparator downloads the baseline images into a temporary prefixed - // directory (sourceDir) instead of the default assets directory (destinationDir). - if (sourceDir.existsSync()) { - _copyDirectory(sourceDir, destinationDir); - } - final String gradle = path.absolute( path.join(androidDir, Platform.isWindows ? 'gradlew.bat' : 'gradlew'), ); @@ -117,10 +96,5 @@ Future runAndroidHardwareSmokeTests({ // Clean up copied goldens to keep Git worktree completely clean _cleanGoldensDirectory(destinationDir); - - // Clean up the temporary prefixed goldens directory - if (sourceDir.existsSync()) { - sourceDir.deleteSync(recursive: true); - } } } diff --git a/dev/integration_tests/android_hardware_smoke_test/test_driver/driver_test.dart b/dev/integration_tests/android_hardware_smoke_test/test_driver/driver_test.dart index ffa137cfeb1fb..69bfae78532e7 100644 --- a/dev/integration_tests/android_hardware_smoke_test/test_driver/driver_test.dart +++ b/dev/integration_tests/android_hardware_smoke_test/test_driver/driver_test.dart @@ -29,13 +29,20 @@ void main() async { final String response = await flutterDriver.requestData( json.encode({keyCommand: commandGetGoldenVariant}), ); - final Map reply = (json.decode(response) as Map) - .cast(); + final Map reply = + (json.decode(response) as Map) + .cast(); final replyVariant = reply[keyGoldenVariant] as String?; - activeGoldenVariant = (replyVariant != null && replyVariant.isNotEmpty) ? '.$replyVariant' : ''; + activeGoldenVariant = switch (replyVariant) { + final String s when s.isNotEmpty => '.$s', + _ => '', + }; if (isLuci) { - await enableSkiaGoldComparator(namePrefix: 'android_hardware_smoke_test$activeGoldenVariant'); + await enableSkiaGoldComparator( + namePrefix: 'android_hardware_smoke_test$activeGoldenVariant', + localOutputDir: 'goldens', + ); } }); @@ -47,12 +54,16 @@ void main() async { Future templateTest(String testName) async { // Ask the app to render the test and return the rendered image bytes final String response = await flutterDriver.requestData( - json.encode({keyTestName: testName, keyPerformAppSideGoldenCompare: false}), + json.encode({ + keyTestName: testName, + keyPerformAppSideGoldenCompare: false, + }), ); // Expect a successful reply or skip status - final Map reply = (json.decode(response) as Map) - .cast(); + final Map reply = + (json.decode(response) as Map) + .cast(); if (reply[keyMessage] == 'Skipped') { markTestSkipped('Skipping $testName: ${reply[keyReason]}'); @@ -74,14 +85,27 @@ void main() async { final img.Image? decoded = img.decodePng(fullBytes); if (decoded == null) { - throw StateError('Failed to decode full screen screenshot for $testName'); + throw StateError( + 'Failed to decode full screen screenshot for $testName', + ); } - if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > decoded.width || y + h > decoded.height) { + if (x < 0 || + y < 0 || + w <= 0 || + h <= 0 || + x + w > decoded.width || + y + h > decoded.height) { throw StateError( 'Crop bounds out of range for $testName: x=$x, y=$y, w=$w, h=$h, image.width=${decoded.width}, image.height=${decoded.height}', ); } - final img.Image cropped = img.copyCrop(decoded, x: x, y: y, width: w, height: h); + final img.Image cropped = img.copyCrop( + decoded, + x: x, + y: y, + width: w, + height: h, + ); imageBytes = Uint8List.fromList(img.encodePng(cropped)); } else { final imageBase64 = reply[keyImageBytes]! as String; @@ -89,7 +113,10 @@ void main() async { } // Compare the bytes to a golden file on the host filesystem using the cached variant - await expectLater(imageBytes, matchesGoldenFile('goldens/$testName$activeGoldenVariant.png')); + await expectLater( + imageBytes, + matchesGoldenFile('goldens/$testName$activeGoldenVariant.png'), + ); } test('should render and match blueRectangleTest golden', () async { @@ -116,15 +143,27 @@ void main() async { await templateTest('backdropFilterBlurTest'); }, timeout: Timeout.none); - test('should render and match $kPlatformViewTextureLayerTest golden', () async { - await templateTest(kPlatformViewTextureLayerTest); - }, timeout: Timeout.none); - - test('should render and match $kPlatformViewHybridCompositionTest golden', () async { - await templateTest(kPlatformViewHybridCompositionTest); - }, timeout: Timeout.none); - - test('should render and match $kPlatformViewHybridCompositionPlusPlusTest golden', () async { - await templateTest(kPlatformViewHybridCompositionPlusPlusTest); - }, timeout: Timeout.none); + test( + 'should render and match $kPlatformViewTextureLayerTest golden', + () async { + await templateTest(kPlatformViewTextureLayerTest); + }, + timeout: Timeout.none, + ); + + test( + 'should render and match $kPlatformViewHybridCompositionTest golden', + () async { + await templateTest(kPlatformViewHybridCompositionTest); + }, + timeout: Timeout.none, + ); + + test( + 'should render and match $kPlatformViewHybridCompositionPlusPlusTest golden', + () async { + await templateTest(kPlatformViewHybridCompositionPlusPlusTest); + }, + timeout: Timeout.none, + ); } diff --git a/dev/tools/android_driver_extensions/lib/skia_gold.dart b/dev/tools/android_driver_extensions/lib/skia_gold.dart index a326891b0f5c9..909fd2563a9f4 100644 --- a/dev/tools/android_driver_extensions/lib/skia_gold.dart +++ b/dev/tools/android_driver_extensions/lib/skia_gold.dart @@ -35,7 +35,11 @@ const String _kGoldctlPresubmitKey = 'GOLD_TRYJOB'; /// for more information. /// /// May optionally provide a [namePrefix] to be used when uploading images. -Future enableSkiaGoldComparator({String? namePrefix}) async { +/// +/// If [localOutputDir] is provided, local screenshots downloaded from Skia Gold +/// will be stored flat in this directory relative to the script execution path +/// (nested subdirectories in golden file URIs are not currently supported). +Future enableSkiaGoldComparator({String? namePrefix, String? localOutputDir}) async { assert( goldenFileComparator is NaiveLocalFileComparator, 'The flutter_goldens_fork library should be used from a *_test.dart file ' @@ -75,31 +79,44 @@ Future enableSkiaGoldComparator({String? namePrefix}) async { skiaGoldClient, namePrefix: namePrefix, presubmit: isPresubmit, + localOutputDir: localOutputDir, ); } /// Configures [goldenFileComparator] to use Skia Gold (for unit testing). +/// +/// If [localOutputDir] is provided, local screenshots downloaded from Skia Gold +/// will be stored flat in this directory relative to the script execution path +/// (nested subdirectories in golden file URIs are not currently supported). @visibleForTesting Future enableSkiaGoldComparatorForTesting( SkiaGoldClient skiaGoldClient, { required bool presubmit, String? namePrefix, + String? localOutputDir, }) async { await skiaGoldClient.auth(); goldenFileComparator = _SkiaGoldComparator( skiaGoldClient, namePrefix: namePrefix, isPresubmit: presubmit, + localOutputDir: localOutputDir, ); } final class _SkiaGoldComparator extends GoldenFileComparator { - _SkiaGoldComparator(this.skiaClient, {required this.isPresubmit, this.namePrefix, Uri? baseDir}) - : baseDir = baseDir ?? Uri.parse(path.dirname(io.Platform.script.path)); + _SkiaGoldComparator( + this.skiaClient, { + required this.isPresubmit, + this.namePrefix, + this.localOutputDir, + Uri? baseDir, + }) : baseDir = baseDir ?? Uri.directory(path.dirname(io.Platform.script.toFilePath())); final Uri baseDir; final SkiaGoldClient skiaClient; final String? namePrefix; + final String? localOutputDir; final bool isPresubmit; @override @@ -149,6 +166,10 @@ final class _SkiaGoldComparator extends GoldenFileComparator { } io.File _getGoldenFile(Uri uri) { + if (localOutputDir != null) { + final String fileName = uri.pathSegments.last; + return io.File(path.join(baseDir.toFilePath(), localOutputDir, fileName)); + } return io.File.fromUri(baseDir.resolveUri(uri)); } From 5bb20d2bbde4f9ae0265f9463f6b07e5f0d1a9b1 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 27 Jul 2026 18:19:37 +0000 Subject: [PATCH 057/147] Roll Abseil to ff6e8ce3e932 (#189998) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0204a1d2d1d2e..904a1ed849fed 100644 --- a/DEPS +++ b/DEPS @@ -553,7 +553,7 @@ deps = { Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + 'c788c52156f3ef7bc7ab769cb03c110a53ac8fcb', 'engine/src/third_party/abseil-cpp': - Var('chromium_git') + '/chromium/src/third_party/abseil-cpp' + '@' + '564023aa53767b5f60b3a556f0a025b7b7e8241e', + Var('chromium_git') + '/chromium/src/third_party/abseil-cpp' + '@' + 'ff6e8ce3e932c16cebd1611c8fc42c45080a0e55', # Dart packages 'engine/src/flutter/third_party/pkg/archive': From 8747e2ff61de94f230f3ff59d3a8c923dfa97eda Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Mon, 27 Jul 2026 11:22:00 -0700 Subject: [PATCH 058/147] ci: verify_binaries_pre_codesigned part 2 (#190078) macos runners don't let you touch /opt! fixes: #189995 --- .github/actions/composite-flutter-setup/action.yml | 5 +++-- .github/workflows/mac-verify-binaries.yml | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/actions/composite-flutter-setup/action.yml b/.github/actions/composite-flutter-setup/action.yml index 637ced9c3a60e..44c09ff189660 100644 --- a/.github/actions/composite-flutter-setup/action.yml +++ b/.github/actions/composite-flutter-setup/action.yml @@ -140,7 +140,8 @@ runs: - name: Setup PUB_CACHE environment variable shell: bash run: | - echo "PUB_CACHE=/opt/pub-cache" >> $GITHUB_ENV + mkdir -p "$RUNNER_TEMP/pub-cache" + echo "PUB_CACHE=$RUNNER_TEMP/pub-cache" >> "$GITHUB_ENV" # Get the Flutter revision. This is the key for the cache for artifacts # under bin/cache @@ -169,7 +170,7 @@ runs: id: pub-cache with: path: | - /opt/pub-cache + ${{ runner.temp }}/pub-cache ${{ github.workspace }}/**/.dart_tool ${{ github.workspace }}/**/pubspec.lock key: ${{ runner.os }}-pub-${{ steps.pub-deps-hash.outputs.revision }} diff --git a/.github/workflows/mac-verify-binaries.yml b/.github/workflows/mac-verify-binaries.yml index 799ddd43dfdd8..33814f11eb95a 100644 --- a/.github/workflows/mac-verify-binaries.yml +++ b/.github/workflows/mac-verify-binaries.yml @@ -5,6 +5,7 @@ on: branches: [master] merge_group: branches: [master] + workflow_dispatch: jobs: check-engine-changes: From 56c717ca3c53754c4fd22c816741aacb83380a60 Mon Sep 17 00:00:00 2001 From: hellohuanlin <41930132+hellohuanlin@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:09:38 -0700 Subject: [PATCH 059/147] [ios]do not nuke user input path when running uiscene integration test (#186436) As discussed in https://github.com/flutter/flutter/issues/186412#issuecomment-4435590832. > It's safer to only delete the folders/files that flutter itself controls. The script can create the project folder under the path (e.g. generated_project). Then when deleting, we only delete generated_project folder (the "Expected" behavior in the description) Note: 1. The documentation itself is fine tho, so I will keep it untouched. 2. It looks like we don't have infra setup to write unit tests for test scripts, so I will leave it as out-of-scope of this PR. 3. But in the future we should be heading towards dumb logic in test scripts, and leverage Xcode build targets & file memberships instead. 4. I think this is a broader security issue than just this particular script - I wish our flutter/dart tooling could have asked for permission for dangerous operations like this. I filed https://github.com/flutter/flutter/issues/186419 *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* Fixes https://github.com/flutter/flutter/issues/186412 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- dev/devicelab/bin/tasks/module_uiscene_test_ios.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index 1d32fc35cd41c..4d2621ea919f9 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -40,7 +40,7 @@ Future main(List args) async { var destinationOverride = false; if (destination != null) { destinationOverride = true; - destinationDir = Directory(destination); + destinationDir = Directory(path.join(destination, 'flutter_uiscene_test_generated_project')); if (destinationDir.existsSync()) { destinationDir.deleteSync(recursive: true); } From 2256bdd8e17030f3880c2a7e9c459c0a04b1d45b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:26:25 +0000 Subject: [PATCH 060/147] Bump actions/labeler from 6.2.0 to 7.0.0 in the all-github-actions group (#190099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the all-github-actions group with 1 update: [actions/labeler](https://github.com/actions/labeler). Updates `actions/labeler` from 6.2.0 to 7.0.0
Release notes

Sourced from actions/labeler's releases.

v7.0.0

What's Changed

Enhancements:

Full Changelog: https://github.com/actions/labeler/compare/v6...v7.0.0

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/labeler&package-manager=github_actions&previous-version=6.2.0&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 32f7f7fa70817..d60b57f69330f 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: # Source available at https://github.com/actions/labeler/blob/main/README.md - - uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d + - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 with: sync-labels: true From 2b4b7054507d169aa2628e5d269620645c729aa2 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 27 Jul 2026 14:32:16 -0700 Subject: [PATCH 061/147] Move tool host_cross_arch tests into different shards (#189470) The intention of the tool_host_cross_arch_tests shard was to run architecture-specific tests that should be run on ARM and Intel. Since we are deprecating Intel Macs and they will only be run on one architecture, ARM, remove the shard entirely. Keep the builder and test runner around as a no-op so nothing breaks in releases. We can remove them totally (builder, TESTOWNER, test runner, etc) once this code reaches stable. https://github.com/flutter/flutter/issues/188328 First part of https://github.com/flutter/flutter/issues/189302 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .ci.yaml | 3 + dev/bots/test.dart | 10 +- .../host_cross_arch.shard/cache_test.dart | 58 ---- .../macos_content_validation_test.dart | 307 ----------------- .../test/integration.shard/cache_test.dart | 42 +++ .../ios_content_validation_test.dart | 4 +- .../macos_content_validation_test.dart | 309 ++++++++++++++++++ 7 files changed, 358 insertions(+), 375 deletions(-) delete mode 100644 packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart delete mode 100644 packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart rename packages/flutter_tools/test/{host_cross_arch.shard => integration.shard}/ios_content_validation_test.dart (99%) create mode 100644 packages/flutter_tools/test/integration.shard/macos_content_validation_test.dart diff --git a/.ci.yaml b/.ci.yaml index c014e7a41826d..4980770c69734 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -4782,6 +4782,9 @@ targets: - name: Mac_arm64 tool_host_cross_arch_tests recipe: flutter/flutter_drone timeout: 60 + enabled_branches: + - beta # TODO(flutter/flutter#189302): Remove after 3.48.0-0.1.pre is released. + - stable # TODO(flutter/flutter#189302): Delete this builder after 3.50 is released. properties: dependencies: >- [ diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 8ab0284582fcb..f76a4d59b4330 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -230,14 +230,8 @@ Future _runWebToolTests() async { ); } -Future _runToolHostCrossArchTests() { - return runDartTest( - _toolsPath, - // These are integration tests - forceSingleCore: true, - testPaths: [path.join('test', 'host_cross_arch.shard')], - ); -} +// TODO(jmagman): https://github.com/flutter/flutter/issues/189302 remove when it gets to stable. +Future _runToolHostCrossArchTests() async {} Future _runIntegrationToolTests() async { final List allTests = Directory(path.join(_toolsPath, 'test', 'integration.shard')) diff --git a/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart b/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart deleted file mode 100644 index 7311412e9ab2c..0000000000000 --- a/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart +++ /dev/null @@ -1,58 +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 'package:file/file.dart'; -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/os.dart'; - -import '../integration.shard/test_utils.dart'; -import '../src/common.dart'; - -Future main() async { - test('verify the dart binary arch matches the host arch', () async { - final HostPlatform dartArch = _identifyMacBinaryArch(_dartBinary.path); - final os = OperatingSystemUtils( - processManager: processManager, - fileSystem: fileSystem, - platform: platform, - logger: BufferLogger.test(), - ); - expect(dartArch, os.hostPlatform); - }, skip: !platform.isMacOS); // [intended] Calls macOS-specific commands -} - -// Call `file` on the path and parse the output. -HostPlatform _identifyMacBinaryArch(String path) { - // Expect STDOUT like: - // bin/cache/dart-sdk/bin/dart: Mach-O 64-bit executable x86_64 - final pattern = RegExp(r'Mach-O 64-bit executable (\w+)'); - final ProcessResult result = processManager.runSync(['file', _dartBinary.path]); - expect( - result, - ProcessResultMatcher(stdoutPattern: '${_dartBinary.path}: Mach-O 64-bit executable'), - ); - final RegExpMatch? match = pattern.firstMatch(result.stdout as String); - if (match == null) { - fail('Unrecognized STDOUT from `file`: "${result.stdout}"'); - } - switch (match.group(1)) { - case 'x86_64': - return HostPlatform.darwin_x64; - case 'arm64': - return HostPlatform.darwin_arm64; - default: - fail('Unexpected architecture ${match.group(1)}'); - } -} - -final String _flutterRootPath = getFlutterRoot(); -final Directory _flutterRoot = fileSystem.directory(_flutterRootPath); -final File _dartBinary = _flutterRoot - .childDirectory('bin') - .childDirectory('cache') - .childDirectory('dart-sdk') - .childDirectory('bin') - .childFile('dart') - .absolute; diff --git a/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart b/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart deleted file mode 100644 index c578a26f037b6..0000000000000 --- a/packages/flutter_tools/test/host_cross_arch.shard/macos_content_validation_test.dart +++ /dev/null @@ -1,307 +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 'package:file_testing/file_testing.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/build_info.dart'; - -import '../integration.shard/test_utils.dart'; -import '../src/common.dart'; - -void main() { - final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); - - setUpAll(() { - processManager.runSync([flutterBin, 'config', '--enable-macos-desktop']); - }); - - for (final buildMode in [BuildMode.debug, BuildMode.profile, BuildMode.release]) { - test('verify ${buildMode.cliName} FlutterMacOS.xcframework artifact', () { - final String flutterRoot = getFlutterRoot(); - - final String artifactDir; - switch (buildMode) { - case BuildMode.debug: - case BuildMode.jitRelease: - artifactDir = 'darwin-x64'; - case BuildMode.profile: - artifactDir = 'darwin-x64-profile'; - case BuildMode.release: - artifactDir = 'darwin-x64-release'; - } - final Directory xcframeworkArtifact = fileSystem.directory( - fileSystem.path.join( - flutterRoot, - 'bin', - 'cache', - 'artifacts', - 'engine', - artifactDir, - 'FlutterMacOS.xcframework', - ), - ); - - final Directory tempDir = createResolvedTempDirectorySync('macos_content_validation.'); - - // Pre-cache macOS engine FlutterMacOS.xcframework artifacts. - final ProcessResult result = processManager.runSync([ - flutterBin, - ...getLocalEngineArguments(), - 'precache', - '--macos', - ], workingDirectory: tempDir.path); - - expect(result, const ProcessResultMatcher()); - expect(xcframeworkArtifact.existsSync(), isTrue); - - final Directory frameworkArtifact = fileSystem.directory( - fileSystem.path.joinAll([ - xcframeworkArtifact.path, - 'macos-arm64_x86_64', - 'FlutterMacOS.framework', - ]), - ); - // Check read/write permissions are set correctly in the framework engine artifact. - final String artifactStat = frameworkArtifact.statSync().mode.toRadixString(8); - expect(artifactStat, '40755'); - - // Verify Info.plist has correct engine version and build mode - final File engineInfo = fileSystem.file( - fileSystem.path.join(flutterRoot, 'bin', 'cache', 'engine_stamp.json'), - ); - expect(engineInfo, exists); - - final String engineVersion; - if (json.decode(engineInfo.readAsStringSync().trim()) as Map case { - 'git_revision': final String parsedVersion, - }) { - engineVersion = parsedVersion; - } else { - fail('engine_stamp.json missing "git_revision" key'); - } - - final File infoPlist = fileSystem.file( - fileSystem.path.joinAll([ - xcframeworkArtifact.path, - 'macos-arm64_x86_64', - 'FlutterMacOS.framework', - 'Versions', - 'A', - 'Resources', - 'Info.plist', - ]), - ); - expect(infoPlist, exists); - - final String infoPlistContents = infoPlist.readAsStringSync(); - expect(infoPlistContents, contains(engineVersion)); - expect(infoPlistContents, contains(buildMode.cliName)); - - if (buildMode == BuildMode.release) { - final Directory dsymArtifact = fileSystem.directory( - fileSystem.path.joinAll([ - xcframeworkArtifact.path, - 'macos-arm64_x86_64', - 'dSYMs', - 'FlutterMacOS.framework.dSYM', - ]), - ); - // Verify dSYM is present. - expect(dsymArtifact.existsSync(), isTrue); - - // Check read/write permissions are set correctly in the framework engine artifact. - final String artifactStat = dsymArtifact.statSync().mode.toRadixString(8); - expect(artifactStat, '40755'); - } - }); - } - - for (final buildMode in ['Debug', 'Release']) { - final String buildModeLower = buildMode.toLowerCase(); - - test('flutter build macos --$buildModeLower builds a valid app', () { - final String workingDirectory = fileSystem.path.join( - getFlutterRoot(), - 'dev', - 'integration_tests', - 'flutter_gallery', - ); - - processManager.runSync([ - flutterBin, - ...getLocalEngineArguments(), - 'clean', - ], workingDirectory: workingDirectory); - - final File podfile = fileSystem.file( - fileSystem.path.join(workingDirectory, 'macos', 'Podfile'), - ); - final File podfileLock = fileSystem.file( - fileSystem.path.join(workingDirectory, 'macos', 'Podfile.lock'), - ); - expect(podfile, exists); - expect(podfileLock, exists); - - // Simulate a newer Podfile than Podfile.lock. - podfile.setLastModifiedSync(DateTime.now()); - podfileLock.setLastModifiedSync(DateTime.now().subtract(const Duration(days: 1))); - expect(podfileLock.lastModifiedSync().isBefore(podfile.lastModifiedSync()), isTrue); - - final buildCommand = [ - flutterBin, - ...getLocalEngineArguments(), - 'build', - 'macos', - '--$buildModeLower', - ]; - final ProcessResult result = processManager.runSync( - buildCommand, - workingDirectory: workingDirectory, - ); - - printOnFailure('Output of flutter build macos:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.exitCode, 0); - - expect(result.stdout, contains('Running pod install')); - expect(podfile.lastModifiedSync().isBefore(podfileLock.lastModifiedSync()), isTrue); - - final Directory buildPath = fileSystem.directory( - fileSystem.path.join(workingDirectory, 'build', 'macos', 'Build', 'Products', buildMode), - ); - - final Directory outputApp = buildPath.childDirectory('Flutter Gallery.app'); - final Directory outputAppFramework = fileSystem.directory( - fileSystem.path.join(outputApp.path, 'Contents', 'Frameworks', 'App.framework'), - ); - - final File frameworkDsymBinary = buildPath.childFile( - 'FlutterMacOS.framework.dSYM/Contents/Resources/DWARF/FlutterMacOS', - ); - - final File libBinary = outputAppFramework.childFile('App'); - final File libDsymBinary = buildPath.childFile( - 'App.framework.dSYM/Contents/Resources/DWARF/App', - ); - - _checkFatBinary(libBinary, buildModeLower, 'dynamically linked shared library'); - - final List libSymbols = AppleTestUtils.getExportedSymbols(libBinary.path); - - if (buildMode == 'Debug') { - // Framework dSYM is not copied for debug builds. - expect(frameworkDsymBinary.existsSync(), isFalse); - - // dSYM is not created for a debug build. - expect(libDsymBinary.existsSync(), isFalse); - expect(libSymbols, isEmpty); - } else { - // Check framework dSYM file copied. - _checkFatBinary(frameworkDsymBinary, buildModeLower, 'dSYM companion file'); - - // Check extracted dSYM file. - _checkFatBinary(libDsymBinary, buildModeLower, 'dSYM companion file'); - expect(libSymbols, equals(AppleTestUtils.requiredSymbols)); - final List dSymSymbols = AppleTestUtils.getExportedSymbols(libDsymBinary.path); - expect(dSymSymbols, containsAll(AppleTestUtils.requiredSymbols)); - // The actual number of symbols is going to vary but there should - // be "many" in the dSYM. At the time of writing, it was 19195. - expect(dSymSymbols.length, greaterThanOrEqualTo(15000)); - } - - expect(outputAppFramework.childLink('Resources'), exists); - - final File vmSnapshot = fileSystem.file( - fileSystem.path.join( - outputApp.path, - 'Contents', - 'Frameworks', - 'App.framework', - 'Resources', - 'flutter_assets', - 'vm_snapshot_data', - ), - ); - - expect(vmSnapshot.existsSync(), buildMode == 'Debug'); - - final Directory outputFlutterFramework = fileSystem.directory( - fileSystem.path.join(outputApp.path, 'Contents', 'Frameworks', 'FlutterMacOS.framework'), - ); - - // Check read/write permissions are being correctly set. - final String outputFrameworkStat = outputFlutterFramework.statSync().mode.toRadixString(8); - expect(outputFrameworkStat, '40755'); - - // Check complicated macOS framework symlink structure. - final Link current = outputFlutterFramework.childDirectory('Versions').childLink('Current'); - - expect(current.targetSync(), 'A'); - - expect( - outputFlutterFramework.childLink('FlutterMacOS').targetSync(), - fileSystem.path.join('Versions', 'Current', 'FlutterMacOS'), - ); - - expect(outputFlutterFramework.childLink('Resources'), exists); - expect( - outputFlutterFramework.childLink('Resources').targetSync(), - fileSystem.path.join('Versions', 'Current', 'Resources'), - ); - - expect(outputFlutterFramework.childLink('Headers'), isNot(exists)); - expect(outputFlutterFramework.childDirectory('Headers'), isNot(exists)); - expect(outputFlutterFramework.childLink('Modules'), isNot(exists)); - expect(outputFlutterFramework.childDirectory('Modules'), isNot(exists)); - - // PrivacyInfo.xcprivacy was first added to the top-level path, but - // the correct location is Versions/A/Resources/PrivacyInfo.xcprivacy. - // TODO(jmagman): Switch expectation to only check Resources/ once the new path rolls. - // https://github.com/flutter/flutter/issues/157016#issuecomment-2420786225 - final File topLevelPrivacy = outputFlutterFramework.childFile('PrivacyInfo.xcprivacy'); - final File resourcesLevelPrivacy = fileSystem.file( - fileSystem.path.join(outputFlutterFramework.path, 'Resources', 'PrivacyInfo.xcprivacy'), - ); - - expect(topLevelPrivacy.existsSync() || resourcesLevelPrivacy.existsSync(), isTrue); - - // Build again without cleaning. - final ProcessResult secondBuild = processManager.runSync( - buildCommand, - workingDirectory: workingDirectory, - ); - - printOnFailure('Output of second build:'); - printOnFailure(secondBuild.stdout.toString()); - printOnFailure(secondBuild.stderr.toString()); - expect(secondBuild.exitCode, 0); - - expect(secondBuild.stdout, isNot(contains('Running pod install'))); - - processManager.runSync([ - flutterBin, - ...getLocalEngineArguments(), - 'clean', - ], workingDirectory: workingDirectory); - }, skip: !platform.isMacOS); // [intended] only makes sense for macos platform. - } -} - -void _checkFatBinary(File file, String buildModeLower, String expectedType) { - final archs = processManager.runSync(['file', file.path]).stdout as String; - - final bool containsX64 = archs.contains('Mach-O 64-bit $expectedType x86_64'); - final bool containsArm = archs.contains('Mach-O 64-bit $expectedType arm64'); - if (buildModeLower == 'debug') { - // Only build the architecture matching the machine running this test, not both. - expect(containsX64 ^ containsArm, isTrue, reason: 'Unexpected architecture $archs'); - } else { - expect(containsX64, isTrue, reason: 'Unexpected architecture $archs'); - expect(containsArm, isTrue, reason: 'Unexpected architecture $archs'); - } -} diff --git a/packages/flutter_tools/test/integration.shard/cache_test.dart b/packages/flutter_tools/test/integration.shard/cache_test.dart index ea115fd8c1fb3..3957ade6deb56 100644 --- a/packages/flutter_tools/test/integration.shard/cache_test.dart +++ b/packages/flutter_tools/test/integration.shard/cache_test.dart @@ -182,6 +182,17 @@ Future main(List args) async { .replaceAll('x86_64', 'x64'); expect(dartTargetArch, equals(unameArch)); }); + + testWithoutContext('verify the dart binary arch matches the host arch', () async { + final HostPlatform dartArch = _identifyMacBinaryArch(_dartBinary.path); + final os = OperatingSystemUtils( + processManager: processManager, + fileSystem: fileSystem, + platform: platform, + logger: BufferLogger.test(), + ); + expect(dartArch, os.hostPlatform); + }, skip: !platform.isMacOS); // [intended] Calls macOS-specific commands } class FakeArtifactUpdater extends Fake implements ArtifactUpdater { @@ -221,3 +232,34 @@ class FakeVersionlessArtifact extends CachedArtifact { OperatingSystemUtils operatingSystemUtils, ) async {} } + +// Call `file` on the path and parse the output. +HostPlatform _identifyMacBinaryArch(String path) { + // Expect STDOUT like: + // bin/cache/dart-sdk/bin/dart: Mach-O 64-bit executable x86_64 + final pattern = RegExp(r'Mach-O 64-bit executable (\w+)'); + final ProcessResult result = processManager.runSync(['file', path]); + expect(result, ProcessResultMatcher(stdoutPattern: '$path: Mach-O 64-bit executable')); + final RegExpMatch? match = pattern.firstMatch(result.stdout as String); + if (match == null) { + fail('Unrecognized STDOUT from `file`: "${result.stdout}"'); + } + switch (match.group(1)) { + case 'x86_64': + return HostPlatform.darwin_x64; + case 'arm64': + return HostPlatform.darwin_arm64; + default: + fail('Unexpected architecture ${match.group(1)}'); + } +} + +final String _flutterRootPath = getFlutterRoot(); +final Directory _flutterRoot = fileSystem.directory(_flutterRootPath); +final File _dartBinary = _flutterRoot + .childDirectory('bin') + .childDirectory('cache') + .childDirectory('dart-sdk') + .childDirectory('bin') + .childFile('dart') + .absolute; diff --git a/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart b/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart similarity index 99% rename from packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart rename to packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart index 8719a1239ec42..1c04eeec03367 100644 --- a/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart +++ b/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart @@ -10,8 +10,8 @@ import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/darwin/darwin.dart'; -import '../integration.shard/test_utils.dart'; import '../src/common.dart'; +import 'test_utils.dart'; void main() { group( @@ -333,7 +333,7 @@ void main() { for (final buildMode in [BuildMode.debug, BuildMode.profile, BuildMode.release]) { for (final arch in ['ios-arm64', 'ios-arm64_x86_64-simulator']) { - test('verify ${buildMode.cliName} $arch Flutter.framework Info.plist', () { + testWithoutContext('verify ${buildMode.cliName} $arch Flutter.framework Info.plist', () { final String artifactDir; switch (buildMode) { case BuildMode.debug: diff --git a/packages/flutter_tools/test/integration.shard/macos_content_validation_test.dart b/packages/flutter_tools/test/integration.shard/macos_content_validation_test.dart new file mode 100644 index 0000000000000..eba8334517175 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/macos_content_validation_test.dart @@ -0,0 +1,309 @@ +// 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:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/build_info.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +void main() { + group('macOS content validation', () { + final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + + setUpAll(() { + processManager.runSync([flutterBin, 'config', '--enable-macos-desktop']); + }); + + for (final buildMode in [BuildMode.debug, BuildMode.profile, BuildMode.release]) { + testWithoutContext('verify ${buildMode.cliName} FlutterMacOS.xcframework artifact', () { + final String flutterRoot = getFlutterRoot(); + + final String artifactDir; + switch (buildMode) { + case BuildMode.debug: + case BuildMode.jitRelease: + artifactDir = 'darwin-x64'; + case BuildMode.profile: + artifactDir = 'darwin-x64-profile'; + case BuildMode.release: + artifactDir = 'darwin-x64-release'; + } + final Directory xcframeworkArtifact = fileSystem.directory( + fileSystem.path.join( + flutterRoot, + 'bin', + 'cache', + 'artifacts', + 'engine', + artifactDir, + 'FlutterMacOS.xcframework', + ), + ); + + final Directory tempDir = createResolvedTempDirectorySync('macos_content_validation.'); + + // Pre-cache macOS engine FlutterMacOS.xcframework artifacts. + final ProcessResult result = processManager.runSync([ + flutterBin, + ...getLocalEngineArguments(), + 'precache', + '--macos', + ], workingDirectory: tempDir.path); + + expect(result, const ProcessResultMatcher()); + expect(xcframeworkArtifact.existsSync(), isTrue); + + final Directory frameworkArtifact = fileSystem.directory( + fileSystem.path.joinAll([ + xcframeworkArtifact.path, + 'macos-arm64_x86_64', + 'FlutterMacOS.framework', + ]), + ); + // Check read/write permissions are set correctly in the framework engine artifact. + final String artifactStat = frameworkArtifact.statSync().mode.toRadixString(8); + expect(artifactStat, '40755'); + + // Verify Info.plist has correct engine version and build mode + final File engineInfo = fileSystem.file( + fileSystem.path.join(flutterRoot, 'bin', 'cache', 'engine_stamp.json'), + ); + expect(engineInfo, exists); + + final String engineVersion; + if (json.decode(engineInfo.readAsStringSync().trim()) as Map case { + 'git_revision': final String parsedVersion, + }) { + engineVersion = parsedVersion; + } else { + fail('engine_stamp.json missing "git_revision" key'); + } + + final File infoPlist = fileSystem.file( + fileSystem.path.joinAll([ + xcframeworkArtifact.path, + 'macos-arm64_x86_64', + 'FlutterMacOS.framework', + 'Versions', + 'A', + 'Resources', + 'Info.plist', + ]), + ); + expect(infoPlist, exists); + + final String infoPlistContents = infoPlist.readAsStringSync(); + expect(infoPlistContents, contains(engineVersion)); + expect(infoPlistContents, contains(buildMode.cliName)); + + if (buildMode == BuildMode.release) { + final Directory dsymArtifact = fileSystem.directory( + fileSystem.path.joinAll([ + xcframeworkArtifact.path, + 'macos-arm64_x86_64', + 'dSYMs', + 'FlutterMacOS.framework.dSYM', + ]), + ); + // Verify dSYM is present. + expect(dsymArtifact.existsSync(), isTrue); + + // Check read/write permissions are set correctly in the framework engine artifact. + final String artifactStat = dsymArtifact.statSync().mode.toRadixString(8); + expect(artifactStat, '40755'); + } + }); + } + + for (final buildMode in ['Debug', 'Release']) { + final String buildModeLower = buildMode.toLowerCase(); + + testWithoutContext('flutter build macos --$buildModeLower builds a valid app', () { + final String workingDirectory = fileSystem.path.join( + getFlutterRoot(), + 'dev', + 'integration_tests', + 'flutter_gallery', + ); + + processManager.runSync([ + flutterBin, + ...getLocalEngineArguments(), + 'clean', + ], workingDirectory: workingDirectory); + + final File podfile = fileSystem.file( + fileSystem.path.join(workingDirectory, 'macos', 'Podfile'), + ); + final File podfileLock = fileSystem.file( + fileSystem.path.join(workingDirectory, 'macos', 'Podfile.lock'), + ); + expect(podfile, exists); + expect(podfileLock, exists); + + // Simulate a newer Podfile than Podfile.lock. + podfile.setLastModifiedSync(DateTime.now()); + podfileLock.setLastModifiedSync(DateTime.now().subtract(const Duration(days: 1))); + expect(podfileLock.lastModifiedSync().isBefore(podfile.lastModifiedSync()), isTrue); + + final buildCommand = [ + flutterBin, + ...getLocalEngineArguments(), + 'build', + 'macos', + '--$buildModeLower', + ]; + final ProcessResult result = processManager.runSync( + buildCommand, + workingDirectory: workingDirectory, + ); + + printOnFailure('Output of flutter build macos:'); + printOnFailure(result.stdout.toString()); + printOnFailure(result.stderr.toString()); + expect(result.exitCode, 0); + + expect(result.stdout, contains('Running pod install')); + expect(podfile.lastModifiedSync().isBefore(podfileLock.lastModifiedSync()), isTrue); + + final Directory buildPath = fileSystem.directory( + fileSystem.path.join(workingDirectory, 'build', 'macos', 'Build', 'Products', buildMode), + ); + + final Directory outputApp = buildPath.childDirectory('Flutter Gallery.app'); + final Directory outputAppFramework = fileSystem.directory( + fileSystem.path.join(outputApp.path, 'Contents', 'Frameworks', 'App.framework'), + ); + + final File frameworkDsymBinary = buildPath.childFile( + 'FlutterMacOS.framework.dSYM/Contents/Resources/DWARF/FlutterMacOS', + ); + + final File libBinary = outputAppFramework.childFile('App'); + final File libDsymBinary = buildPath.childFile( + 'App.framework.dSYM/Contents/Resources/DWARF/App', + ); + + _checkFatBinary(libBinary, buildModeLower, 'dynamically linked shared library'); + + final List libSymbols = AppleTestUtils.getExportedSymbols(libBinary.path); + + if (buildMode == 'Debug') { + // Framework dSYM is not copied for debug builds. + expect(frameworkDsymBinary.existsSync(), isFalse); + + // dSYM is not created for a debug build. + expect(libDsymBinary.existsSync(), isFalse); + expect(libSymbols, isEmpty); + } else { + // Check framework dSYM file copied. + _checkFatBinary(frameworkDsymBinary, buildModeLower, 'dSYM companion file'); + + // Check extracted dSYM file. + _checkFatBinary(libDsymBinary, buildModeLower, 'dSYM companion file'); + expect(libSymbols, equals(AppleTestUtils.requiredSymbols)); + final List dSymSymbols = AppleTestUtils.getExportedSymbols(libDsymBinary.path); + expect(dSymSymbols, containsAll(AppleTestUtils.requiredSymbols)); + // The actual number of symbols is going to vary but there should + // be "many" in the dSYM. At the time of writing, it was 19195. + expect(dSymSymbols.length, greaterThanOrEqualTo(15000)); + } + + expect(outputAppFramework.childLink('Resources'), exists); + + final File vmSnapshot = fileSystem.file( + fileSystem.path.join( + outputApp.path, + 'Contents', + 'Frameworks', + 'App.framework', + 'Resources', + 'flutter_assets', + 'vm_snapshot_data', + ), + ); + + expect(vmSnapshot.existsSync(), buildMode == 'Debug'); + + final Directory outputFlutterFramework = fileSystem.directory( + fileSystem.path.join(outputApp.path, 'Contents', 'Frameworks', 'FlutterMacOS.framework'), + ); + + // Check read/write permissions are being correctly set. + final String outputFrameworkStat = outputFlutterFramework.statSync().mode.toRadixString(8); + expect(outputFrameworkStat, '40755'); + + // Check complicated macOS framework symlink structure. + final Link current = outputFlutterFramework.childDirectory('Versions').childLink('Current'); + + expect(current.targetSync(), 'A'); + + expect( + outputFlutterFramework.childLink('FlutterMacOS').targetSync(), + fileSystem.path.join('Versions', 'Current', 'FlutterMacOS'), + ); + + expect(outputFlutterFramework.childLink('Resources'), exists); + expect( + outputFlutterFramework.childLink('Resources').targetSync(), + fileSystem.path.join('Versions', 'Current', 'Resources'), + ); + + expect(outputFlutterFramework.childLink('Headers'), isNot(exists)); + expect(outputFlutterFramework.childDirectory('Headers'), isNot(exists)); + expect(outputFlutterFramework.childLink('Modules'), isNot(exists)); + expect(outputFlutterFramework.childDirectory('Modules'), isNot(exists)); + + // PrivacyInfo.xcprivacy was first added to the top-level path, but + // the correct location is Versions/A/Resources/PrivacyInfo.xcprivacy. + // TODO(jmagman): Switch expectation to only check Resources/ once the new path rolls. + // https://github.com/flutter/flutter/issues/157016#issuecomment-2420786225 + final File topLevelPrivacy = outputFlutterFramework.childFile('PrivacyInfo.xcprivacy'); + final File resourcesLevelPrivacy = fileSystem.file( + fileSystem.path.join(outputFlutterFramework.path, 'Resources', 'PrivacyInfo.xcprivacy'), + ); + + expect(topLevelPrivacy.existsSync() || resourcesLevelPrivacy.existsSync(), isTrue); + + // Build again without cleaning. + final ProcessResult secondBuild = processManager.runSync( + buildCommand, + workingDirectory: workingDirectory, + ); + + printOnFailure('Output of second build:'); + printOnFailure(secondBuild.stdout.toString()); + printOnFailure(secondBuild.stderr.toString()); + expect(secondBuild.exitCode, 0); + + expect(secondBuild.stdout, isNot(contains('Running pod install'))); + + processManager.runSync([ + flutterBin, + ...getLocalEngineArguments(), + 'clean', + ], workingDirectory: workingDirectory); + }); + } + }, skip: !platform.isMacOS); // [intended] macOS content validation only runs on macOS. +} + +void _checkFatBinary(File file, String buildModeLower, String expectedType) { + final archs = processManager.runSync(['file', file.path]).stdout as String; + + final bool containsX64 = archs.contains('Mach-O 64-bit $expectedType x86_64'); + final bool containsArm = archs.contains('Mach-O 64-bit $expectedType arm64'); + if (buildModeLower == 'debug') { + // Only build the architecture matching the machine running this test, not both. + expect(containsX64 ^ containsArm, isTrue, reason: 'Unexpected architecture $archs'); + } else { + expect(containsX64, isTrue, reason: 'Unexpected architecture $archs'); + expect(containsArm, isTrue, reason: 'Unexpected architecture $archs'); + } +} From 5506de29377ec9f5078e8138f8b99a1e6d7c4978 Mon Sep 17 00:00:00 2001 From: flutter-pub-roller-bot <137456488+flutter-pub-roller-bot@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:07:04 -0700 Subject: [PATCH 062/147] Roll pub packages (#189872) This PR was generated by `flutter update-packages --force-upgrade`. --- packages/flutter/pubspec.yaml | 4 ++-- packages/flutter_test/pubspec.yaml | 4 ++-- pubspec.lock | 4 ++-- pubspec.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 9f1feeea5d19f..04a6d3fd2679e 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # See https://github.com/flutter/flutter/issues/185017 material_color_utilities: 0.13.0 meta: ^1.19.0 - vector_math: ^2.4.0 + vector_math: ^2.4.1 sky_engine: sdk: flutter @@ -42,4 +42,4 @@ dev_dependencies: path: any platform: any -# PUBSPEC CHECKSUM: ufigpi +# PUBSPEC CHECKSUM: vrhoum diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 8a00396114b44..9bc56569d1fc4 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: stack_trace: ^1.12.1 # Used by globalToLocal et al. - vector_math: ^2.4.0 + vector_math: ^2.4.1 # Used to detect memory leaks with `testWidgets`. leak_tracker_flutter_testing: ^3.0.10 @@ -48,4 +48,4 @@ dev_dependencies: flutter_driver: sdk: flutter -# PUBSPEC CHECKSUM: lu8vkm +# PUBSPEC CHECKSUM: b83lrh diff --git a/pubspec.lock b/pubspec.lock index b79d2b4411197..7a6dbdd9e8430 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1171,10 +1171,10 @@ packages: dependency: "direct main" description: name: vector_math - sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188" + sha256: "99b6fb0d1881dd1302cd42e0f53bcce99ea246e0a85c8aeb79fb38b283852e88" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" video_player: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 80163c30f82a4..f4e1a6fe3c88e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -190,7 +190,7 @@ dependencies: url_launcher_platform_interface: 2.3.2 url_launcher_web: 2.4.3 url_launcher_windows: 3.1.5 - vector_math: ^2.4.0 + vector_math: ^2.4.1 video_player: 2.13.0 video_player_android: 2.12.0 video_player_avfoundation: 2.11.0 @@ -222,4 +222,4 @@ dependencies: dev_dependencies: ffigen: 20.1.1 -# PUBSPEC CHECKSUM: eb62ub +# PUBSPEC CHECKSUM: 1n10pp From 7adec80e233871bb04efba1b7419edbea32b2e2d Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:18:14 -0700 Subject: [PATCH 063/147] [wimp] fixes ubo padding size issue (#189958) issue https://github.com/flutter/flutter/issues/187212 This clamps the minimum bound size of a UBO to GL_UNIFORM_BLOCK_DATA_SIZE. This was causing a crash in firefox's webgl2 implementation. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../backend/gles/buffer_bindings_gles.cc | 22 +++-- .../backend/gles/buffer_bindings_gles.h | 7 +- .../gles/buffer_bindings_gles_unittests.cc | 96 +++++++++++++++++++ .../renderer/backend/gles/test/mock_gles.cc | 56 +++++++++++ .../renderer/backend/gles/test/mock_gles.h | 48 ++++++++++ 5 files changed, 222 insertions(+), 7 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc index e1ce430e81464..2055e5164edd7 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -130,9 +130,13 @@ bool BufferBindingsGLES::ReadUniformsBindingsV3(const ProcTableGLES& gl, GLuint block_index = gl.GetUniformBlockIndex(program, name.data()); gl.UniformBlockBinding(program_handle_, block_index, i); + GLint block_data_size = 0; + gl.GetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_DATA_SIZE, + &block_data_size); ubo_locations_[std::string{name.data(), static_cast(length)}] = - std::make_pair(block_index, i); + UBOInfo{static_cast(block_index), static_cast(i), + block_data_size}; } use_ubo_ = true; return ReadUniformsBindingsV2(gl, program); @@ -370,15 +374,14 @@ bool BufferBindingsGLES::BindUniformBufferV3( const BufferView& buffer, const ShaderMetadata* metadata, const DeviceBufferGLES& device_buffer_gles) { - absl::flat_hash_map>::iterator it = - ubo_locations_.find(metadata->name); + auto it = ubo_locations_.find(metadata->name); if (it == ubo_locations_.end()) { // This should only happen if we have GLESv3 but are using v2 shaders, // as GLESv3 shaders compiled by impeller always have // **named** uniform buffer blocks return BindUniformBufferV2(gl, buffer, metadata, device_buffer_gles); } - const auto& [block_index, binding_point] = it->second; + const auto& ubo_info = it->second; if (!device_buffer_gles.BindAndUploadDataIfNecessary( DeviceBufferGLES::BindingType::kUniformBuffer)) { return false; @@ -387,8 +390,15 @@ bool BufferBindingsGLES::BindUniformBufferV3( if (!handle.has_value()) { return false; } - gl.BindBufferRange(GL_UNIFORM_BUFFER, binding_point, handle.value(), - buffer.GetRange().offset, buffer.GetRange().length); + size_t length = std::max(buffer.GetRange().length, + static_cast(ubo_info.data_size)); + if (buffer.GetRange().offset + length > + device_buffer_gles.GetDeviceBufferDescriptor().size) { + VALIDATION_LOG << "Uniform buffer range exceeds device buffer size."; + return false; + } + gl.BindBufferRange(GL_UNIFORM_BUFFER, ubo_info.binding_point, handle.value(), + buffer.GetRange().offset, length); return true; } diff --git a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.h index 02cc6a30042cc..12cef0328bc1b 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles.h @@ -82,7 +82,12 @@ class BufferBindingsGLES { std::vector> vertex_attrib_arrays_; absl::flat_hash_map uniform_locations_; - absl::flat_hash_map> ubo_locations_; + struct UBOInfo { + GLint block_index = 0; + GLuint binding_point = 0; + GLint data_size = 0; + }; + absl::flat_hash_map ubo_locations_; using BindingMap = absl::flat_hash_map>; BindingMap binding_map_ = {}; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc index ea39bc2eec436..9bae5b1beb717 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/buffer_bindings_gles_unittests.cc @@ -8,11 +8,15 @@ #include "impeller/renderer/backend/gles/buffer_bindings_gles.h" #include "impeller/renderer/backend/gles/device_buffer_gles.h" #include "impeller/renderer/backend/gles/formats_gles.h" +#include "impeller/renderer/backend/gles/reactor_gles.h" #include "impeller/renderer/backend/gles/test/mock_gles.h" #include "impeller/renderer/command.h" namespace impeller { namespace testing { +namespace { +const GLint kBlockDataSize = 16; +} using ::testing::_; @@ -265,5 +269,97 @@ TEST(BufferBindingsGLESTest, BindVertexAttributesSetsInstanceRateDivisor) { /*instance=*/0)); } +namespace { +class TestWorker : public ReactorGLES::Worker { + public: + bool CanReactorReactOnCurrentThreadNow( + const ReactorGLES& reactor) const override { + return true; + } +}; +} // namespace + +void TestBindUniformBufferRange(size_t buffer_view_length, + size_t expected_bound_size) { + BufferBindingsGLES bindings; + auto mock_gles_impl = std::make_unique<::testing::NiceMock>(); + + const GLuint kProgram = 1; + + ON_CALL(*mock_gles_impl, + GetProgramiv(/*program=*/kProgram, + /*pname=*/GL_ACTIVE_UNIFORM_BLOCKS, /*params=*/_)) + .WillByDefault(::testing::SetArgPointee<2>(1)); + ON_CALL(*mock_gles_impl, + GetActiveUniformBlockiv(/*program=*/kProgram, + /*uniformBlockIndex=*/0, + /*pname=*/GL_UNIFORM_BLOCK_NAME_LENGTH, + /*params=*/_)) + .WillByDefault(::testing::SetArgPointee<3>(9)); + ON_CALL(*mock_gles_impl, + GetActiveUniformBlockName(/*program=*/kProgram, + /*uniformBlockIndex=*/0, + /*bufSize=*/9, /*length=*/_, + /*uniformBlockName=*/_)) + .WillByDefault([](GLuint program, GLuint index, GLsizei bufSize, + GLsizei* length, GLchar* name) { + *length = 8; + std::memcpy(name, "FragInfo", 9); + }); + ON_CALL( + *mock_gles_impl, + GetUniformBlockIndex(/*program=*/kProgram, + /*uniformBlockName=*/::testing::StrEq("FragInfo"))) + .WillByDefault(::testing::Return(0)); + ON_CALL(*mock_gles_impl, + GetActiveUniformBlockiv(/*program=*/kProgram, + /*uniformBlockIndex=*/0, + /*pname=*/GL_UNIFORM_BLOCK_DATA_SIZE, + /*params=*/_)) + .WillByDefault(::testing::SetArgPointee<3>(kBlockDataSize)); + + EXPECT_CALL(*mock_gles_impl, + BindBufferRange(/*target=*/GL_UNIFORM_BUFFER, /*index=*/0, + /*buffer=*/_, /*offset=*/0, + /*size=*/expected_bound_size)) + .Times(1); + + std::shared_ptr mock_gl = MockGLES::Init(std::move(mock_gles_impl)); + ASSERT_TRUE(bindings.ReadUniformsBindings(mock_gl->GetProcTable(), kProgram)); + + ProcTableGLES::Resolver resolver = kMockResolverGLES; + auto proc_table = std::make_unique(resolver); + auto worker = std::make_shared(); + auto reactor = std::make_shared(std::move(proc_table)); + reactor->AddWorker(worker); + + std::vector bound_buffers; + std::vector bound_textures; + + ShaderMetadata shader_metadata = {.name = "FragInfo"}; + auto backing_store = std::make_unique(); + ASSERT_TRUE(backing_store->Truncate(Bytes{1024})); + DeviceBufferGLES device_buffer(DeviceBufferDescriptor{.size = 1024}, reactor, + std::move(backing_store)); + BufferView buffer_view(&device_buffer, Range(0, buffer_view_length)); + bound_buffers.push_back(BufferResource(&shader_metadata, buffer_view)); + + EXPECT_TRUE(bindings.BindUniformData(mock_gl->GetProcTable(), bound_textures, + bound_buffers, Range{0, 0}, + Range{0, 1})); +} + +TEST(BufferBindingsGLESTest, + BindUniformBufferUsesMaxOfBufferViewAndBlockDataSize) { + TestBindUniformBufferRange(/*buffer_view_length=*/4, + /*expected_bound_size=*/kBlockDataSize); +} + +TEST(BufferBindingsGLESTest, + BindUniformBufferUsesBufferViewLengthWhenGreaterThanBlockDataSize) { + TestBindUniformBufferRange(/*buffer_view_length=*/32, + /*expected_bound_size=*/32); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.cc index bec7631eb730e..f4d48151f1537 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.cc @@ -303,6 +303,52 @@ void mockBindTexture(GLenum target, GLuint texture) { static_assert(CheckSameSignature::value); +void mockBindBufferRange(GLenum target, + GLuint index, + GLuint buffer, + GLintptr offset, + GLsizeiptr size) { + CallMockMethod(&IMockGLESImpl::BindBufferRange, target, index, buffer, offset, + size); +} +static_assert(CheckSameSignature::value); + +void mockGetProgramiv(GLuint program, GLenum pname, GLint* params) { + CallMockMethod(&IMockGLESImpl::GetProgramiv, program, pname, params); +} +static_assert(CheckSameSignature::value); + +void mockGetActiveUniformBlockiv(GLuint program, + GLuint uniformBlockIndex, + GLenum pname, + GLint* params) { + CallMockMethod(&IMockGLESImpl::GetActiveUniformBlockiv, program, + uniformBlockIndex, pname, params); +} +static_assert(CheckSameSignature::value); + +void mockGetActiveUniformBlockName(GLuint program, + GLuint uniformBlockIndex, + GLsizei bufSize, + GLsizei* length, + GLchar* uniformBlockName) { + CallMockMethod(&IMockGLESImpl::GetActiveUniformBlockName, program, + uniformBlockIndex, bufSize, length, uniformBlockName); +} +static_assert(CheckSameSignature::value); + +GLuint mockGetUniformBlockIndex(GLuint program, + const GLchar* uniformBlockName) { + return CallMockMethod(&IMockGLESImpl::GetUniformBlockIndex, program, + uniformBlockName); +} +static_assert(CheckSameSignature::value); + GLboolean mockIsTexture(GLuint texture) { return CallMockMethod(&IMockGLESImpl::IsTexture, texture); } @@ -537,6 +583,16 @@ const ProcTableGLES::Resolver kMockResolverGLES = [](const char* name) { return reinterpret_cast(mockDrawElementsInstanced); } else if (strcmp(name, "glVertexAttribDivisor") == 0) { return reinterpret_cast(mockVertexAttribDivisor); + } else if (strcmp(name, "glBindBufferRange") == 0) { + return reinterpret_cast(mockBindBufferRange); + } else if (strcmp(name, "glGetProgramiv") == 0) { + return reinterpret_cast(mockGetProgramiv); + } else if (strcmp(name, "glGetActiveUniformBlockiv") == 0) { + return reinterpret_cast(mockGetActiveUniformBlockiv); + } else if (strcmp(name, "glGetActiveUniformBlockName") == 0) { + return reinterpret_cast(mockGetActiveUniformBlockName); + } else if (strcmp(name, "glGetUniformBlockIndex") == 0) { + return reinterpret_cast(mockGetUniformBlockIndex); } else { return reinterpret_cast(&doNothing); } diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.h index 1fe8ae74a9f42..89fd4dd46fa6a 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/mock_gles.h @@ -125,6 +125,25 @@ class IMockGLESImpl { const void* indices, GLsizei instancecount) {} virtual void VertexAttribDivisor(GLuint index, GLuint divisor) {} + virtual void BindBufferRange(GLenum target, + GLuint index, + GLuint buffer, + GLintptr offset, + GLsizeiptr size) {} + virtual void GetProgramiv(GLuint program, GLenum pname, GLint* params) {} + virtual void GetActiveUniformBlockiv(GLuint program, + GLuint uniformBlockIndex, + GLenum pname, + GLint* params) {} + virtual void GetActiveUniformBlockName(GLuint program, + GLuint uniformBlockIndex, + GLsizei bufSize, + GLsizei* length, + GLchar* uniformBlockName) {} + virtual GLuint GetUniformBlockIndex(GLuint program, + const GLchar* uniformBlockName) { + return 0; + } }; class MockGLESImpl : public IMockGLESImpl { @@ -299,6 +318,35 @@ class MockGLESImpl : public IMockGLESImpl { VertexAttribDivisor, (GLuint index, GLuint divisor), (override)); + MOCK_METHOD(void, + BindBufferRange, + (GLenum target, + GLuint index, + GLuint buffer, + GLintptr offset, + GLsizeiptr size), + (override)); + MOCK_METHOD(void, + GetProgramiv, + (GLuint program, GLenum pname, GLint* params), + (override)); + MOCK_METHOD( + void, + GetActiveUniformBlockiv, + (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params), + (override)); + MOCK_METHOD(void, + GetActiveUniformBlockName, + (GLuint program, + GLuint uniformBlockIndex, + GLsizei bufSize, + GLsizei* length, + GLchar* uniformBlockName), + (override)); + MOCK_METHOD(GLuint, + GetUniformBlockIndex, + (GLuint program, const GLchar* uniformBlockName), + (override)); }; /// @brief Provides a mocked version of the |ProcTableGLES| class. From a23e8598188792f9e71a00ed2d9db27e735fb33e Mon Sep 17 00:00:00 2001 From: Ishaq Hassan Date: Tue, 28 Jul 2026 03:23:16 +0500 Subject: [PATCH 064/147] Add Ishaq Hassan to AUTHORS (#190064) Adds my name to the `AUTHORS` file, as the file itself invites: > Anyone who has contributed to the Flutter project in any way (not limited to submitting PRs) is welcome to submit a PR to add their name to this file. I've had a number of PRs merged into `flutter/flutter`, including: - #185938 Add `blendMode` parameter to `RawImage` and `RenderImage` - #184545 Add `clipBehavior` parameter to `AnimatedCrossFade` - #183109 Add `scrollPadding` property to `DropdownMenu` - #186943, #184569, #184572, #183097, #183081 (docs + template fixes) CLA is already signed (required for the merges above). ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide]. - [x] I signed the [CLA]. - [x] This is a change to the `AUTHORS` file only; no code, tests, or docs are affected ([test-exempt]). [Contributor Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [test-exempt]: https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index a59779edc32cf..bb4526e522dd9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -147,3 +147,4 @@ Michal Kucharski Alexander Dmitriev Solvejet Mohamed Risaldar Uppil Thodi +Ishaq Hassan From 6d3bf8e8055e9df3c976e93949677ffe826f5ac1 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 27 Jul 2026 21:44:19 -0400 Subject: [PATCH 065/147] Roll Fuchsia Linux SDK from vpboK5fPPIoFteqRq... to OZkZC_2CZ_G5rbMIS... (#190115) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jsimmons@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 904a1ed849fed..166884a3ea282 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'vpboK5fPPIoFteqRqfWrqsRlNMTI9Bq7YFY_b51Mf5MC' + 'version': 'OZkZC_2CZ_G5rbMISLtSbLoXmNDU9brV90d5y4UbTXcC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From 1efa3bc53e7533c091bf865e31289708eaa66c3e Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Mon, 27 Jul 2026 18:44:20 -0700 Subject: [PATCH 066/147] Fix hcpp cliprect being behind by 1 frame when scrolling (#189946) Previously, the clipping was behind by 1 frame. This is because `onDisplayPlatformView2()` was getting called after `jni_facade->swapTransaction()`, and so was adding new pending transactions after the swap from pending to active (and so the clips were lagging for a frame, waiting till next frame to be made active). We need to call `jni_facade->swapTransaction()` last. The ordering (c++ bug) was introduced in https://github.com/flutter/flutter/pull/181009/ in combination with https://github.com/flutter/flutter/pull/184732/ See videos:
With Change (After) Without Change (Before)
--- .../external_view_embedder_2.cc | 2 +- .../external_view_embedder_unittests.cc | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc index a9bb173cc8366..cadb90f1459a6 100644 --- a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc +++ b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.cc @@ -177,7 +177,6 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView( } else { HideOverlayLayerIfNeeded(); } - jni_facade->swapTransaction(); for (int64_t view_id : composition_order) { DlRect view_rect = GetViewRect(view_id, view_params); @@ -200,6 +199,7 @@ void AndroidExternalViewEmbedder2::SubmitFlutterView( jni_facade->hidePlatformView2(view_id); } + jni_facade->swapTransaction(); jni_facade_->onEndFrame2(); })); diff --git a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index fd867b27a864e..dc15f144864cf 100644 --- a/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/engine/src/flutter/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -7,6 +7,7 @@ #include #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" +#include "flutter/shell/platform/android/external_view_embedder/external_view_embedder_2.h" #include "flutter/flow/embedded_views.h" #include "flutter/flow/surface.h" @@ -1112,5 +1113,80 @@ TEST(AndroidExternalViewEmbedder, TeardownDoesNotCallJNIMethod) { embedder->Teardown(); } +TEST(AndroidExternalViewEmbedder2, + SwapsTransactionsAfterDisplayingPlatformViews) { + auto jni_mock = std::make_shared(); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform | ThreadHost::Type::kIo | + ThreadHost::Type::kUi | ThreadHost::Type::kRaster); + TaskRunners task_runners( + "test", + thread_host.platform_thread->GetTaskRunner(), // platform + thread_host.raster_thread->GetTaskRunner(), // raster + thread_host.ui_thread->GetTaskRunner(), // ui + thread_host.io_thread->GetTaskRunner() // io + ); + auto surface_factory = std::make_shared([]() { + auto android_surface = std::make_unique(); + EXPECT_CALL(*android_surface, IsValid()).WillRepeatedly(Return(true)); + EXPECT_CALL(*android_surface, SetNativeWindow(_, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*android_surface, CreateGPUSurface(_)) + .WillRepeatedly(Return(ByMove(std::make_unique()))); + return android_surface; + }); + + fml::RefPtr window = + fml::MakeRefCounted(nullptr); + EXPECT_CALL(*jni_mock, createOverlaySurface2()) + .WillRepeatedly(Return( + ByMove(std::make_unique( + 0, window)))); + + auto embedder = std::make_unique( + *android_context, jni_mock, surface_factory, task_runners); + + const DlISize frame_size(100, 100); + const int64_t view_id = 42; + MutatorsStack mutators; + DlMatrix matrix = DlMatrix::MakeTranslation({0, 0}); + + embedder->PrepareFlutterView(frame_size, 1.0); + embedder->PrerollCompositeEmbeddedView( + view_id, + std::make_unique(matrix, DlSize(50, 50), mutators)); + embedder->CompositeEmbeddedView(view_id); + + { + ::testing::InSequence sequence; + + EXPECT_CALL(*jni_mock, onDisplayPlatformView2(view_id, 0, 0, 50, 50, 50, 50, + mutators)); + EXPECT_CALL(*jni_mock, swapTransaction()); + EXPECT_CALL(*jni_mock, onEndFrame2()); + } + + SurfaceFrame::FramebufferInfo framebuffer_info; + auto surface_frame = std::make_unique( + SkSurfaces::Null(100, 100), framebuffer_info, + [](const SurfaceFrame& surface_frame, DlCanvas* canvas) { return true; }, + [](const SurfaceFrame& surface_frame) { return true; }, + /*frame_size=*/frame_size); + + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask(task_runners.GetRasterTaskRunner(), [&]() { + embedder->SubmitFlutterView(kImplicitViewId, nullptr, nullptr, + std::move(surface_frame)); + fml::TaskRunner::RunNowOrPostTask(task_runners.GetPlatformTaskRunner(), + [&latch]() { latch.Signal(); }); + }); + latch.Wait(); + + embedder->Teardown(); + embedder.reset(); +} + } // namespace testing } // namespace flutter From b79b4f9fc3300ec3433a6c60663ea7013d1f80ab Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 28 Jul 2026 11:07:04 +0900 Subject: [PATCH 067/147] iOS,macOS: Rename Swift test files to end in Tests.swift (#190063) Standardizes our Swift test file naming and migrates all `FooTest.swift` files to use the `FooTests.swift` naming used by Xcode's test templates and the rest of the Swift ecosystem. Previously we used a mix of both. ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- engine/src/flutter/shell/platform/darwin/common/BUILD.gn | 4 ++-- .../Source/{LoggerTest.swift => LoggerTests.swift} | 2 +- .../Source/{TracingTest.swift => TracingTests.swift} | 2 +- engine/src/flutter/shell/platform/darwin/ios/BUILD.gn | 8 ++++---- ...llectionTest.swift => ConnectionCollectionTests.swift} | 2 +- ...inkManagerTest.swift => DisplayLinkManagerTests.swift} | 2 +- .../{LaunchEngineTest.swift => LaunchEngineTests.swift} | 2 +- .../{VSyncClientTest.swift => VSyncClientTests.swift} | 2 +- engine/src/flutter/shell/platform/darwin/macos/BUILD.gn | 2 +- ...nchronizerTest.swift => ResizeSynchronizerTests.swift} | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) rename engine/src/flutter/shell/platform/darwin/common/framework/Source/{LoggerTest.swift => LoggerTests.swift} (98%) rename engine/src/flutter/shell/platform/darwin/common/framework/Source/{TracingTest.swift => TracingTests.swift} (97%) rename engine/src/flutter/shell/platform/darwin/ios/framework/Source/{ConnectionCollectionTest.swift => ConnectionCollectionTests.swift} (97%) rename engine/src/flutter/shell/platform/darwin/ios/framework/Source/{DisplayLinkManagerTest.swift => DisplayLinkManagerTests.swift} (98%) rename engine/src/flutter/shell/platform/darwin/ios/framework/Source/{LaunchEngineTest.swift => LaunchEngineTests.swift} (98%) rename engine/src/flutter/shell/platform/darwin/ios/framework/Source/{VSyncClientTest.swift => VSyncClientTests.swift} (99%) rename engine/src/flutter/shell/platform/darwin/macos/framework/Source/{ResizeSynchronizerTest.swift => ResizeSynchronizerTests.swift} (99%) diff --git a/engine/src/flutter/shell/platform/darwin/common/BUILD.gn b/engine/src/flutter/shell/platform/darwin/common/BUILD.gn index eca2e28ecfd74..fb3805429953b 100644 --- a/engine/src/flutter/shell/platform/darwin/common/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/common/BUILD.gn @@ -244,8 +244,8 @@ executable("framework_common_swift_unittests") { ":swift_testing_config", ] sources = [ - "framework/Source/LoggerTest.swift", - "framework/Source/TracingTest.swift", + "framework/Source/LoggerTests.swift", + "framework/Source/TracingTests.swift", ] deps = [ ":framework_common", diff --git a/engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTest.swift b/engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTests.swift similarity index 98% rename from engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTest.swift rename to engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTests.swift index 00ae98d81ff84..9872233df543d 100644 --- a/engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTest.swift +++ b/engine/src/flutter/shell/platform/darwin/common/framework/Source/LoggerTests.swift @@ -8,7 +8,7 @@ import Testing import InternalFlutterSwiftCommon import test_utils_swift -@Suite struct LoggerTest { +@Suite struct LoggerTests { @Test func testInitialization() { let writer = StringOutputWriter() diff --git a/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTest.swift b/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift similarity index 97% rename from engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTest.swift rename to engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift index 62503120db0a1..20c193c529b9b 100644 --- a/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTest.swift +++ b/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift @@ -6,7 +6,7 @@ import Foundation import InternalFlutterSwiftCommon import Testing -@Suite struct TracingTest { +@Suite struct TracingTests { @Test func testTracePlatformVsyncDoesNotCrash() { Tracing.tracePlatformVsync( diff --git a/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn b/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn index acae883b057d1..52f3054d9d8af 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn @@ -255,13 +255,13 @@ if (enable_ios_unittests) { bridge_header = "FlutterTests-Bridging-Header.h" sources = [ "framework/Source/AccessibilityFeaturesTests.swift", - "framework/Source/ConnectionCollectionTest.swift", - "framework/Source/DisplayLinkManagerTest.swift", + "framework/Source/ConnectionCollectionTests.swift", + "framework/Source/DisplayLinkManagerTests.swift", "framework/Source/FakeUIPressProxy.swift", - "framework/Source/LaunchEngineTest.swift", + "framework/Source/LaunchEngineTests.swift", "framework/Source/SplashScreenManagerTests.swift", "framework/Source/TaskRunnerTests.swift", - "framework/Source/VSyncClientTest.swift", + "framework/Source/VSyncClientTests.swift", ] frameworks = [ "Testing.framework" ] deps = [ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTest.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTests.swift similarity index 97% rename from engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTest.swift rename to engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTests.swift index cc7984c9bad86..0f400a195b7ee 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTest.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/ConnectionCollectionTests.swift @@ -5,7 +5,7 @@ import InternalFlutterSwift import Testing -struct ConnectionCollectionTest { +struct ConnectionCollectionTests { @Test func acquireAndRelease() { let connections = ConnectionCollection() let connectionID = connections.acquireConnection(forChannel: "foo") diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift similarity index 98% rename from engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift rename to engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift index 4f693b0b52ac8..198158a1af449 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTest.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift @@ -7,7 +7,7 @@ import XCTest @testable import InternalFlutterSwift @MainActor -class DisplayLinkManagerTest: XCTestCase { +class DisplayLinkManagerTests: XCTestCase { func testDisplayLinkManagerCanBeInstantiatedWithMockValues() { let manager = DisplayLinkManager(maxRefreshRateEnabled: true, refreshRate: 120.0) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTest.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTests.swift similarity index 98% rename from engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTest.swift rename to engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTests.swift index 17f5212311f06..73896f1660588 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTest.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/LaunchEngineTests.swift @@ -6,7 +6,7 @@ import InternalFlutterSwift import Testing @MainActor -struct LaunchEngineTest { +struct LaunchEngineTests { /// Verifies that the engine is lazily created on first access, cached on subsequent accesses, and /// successfully transferred when taken, leaving the container empty. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTest.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift similarity index 99% rename from engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTest.swift rename to engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift index 5935f2feda1c8..d468c23c4962a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTest.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift @@ -6,7 +6,7 @@ import XCTest @testable import InternalFlutterSwift -class VSyncClientTest: XCTestCase { +class VSyncClientTests: XCTestCase { var threadTaskRunner: TaskRunner! override func setUp() { diff --git a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn index ea069e2674648..68891b19e1bb7 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn @@ -201,7 +201,7 @@ executable("flutter_desktop_darwin_swift_unittests") { "//flutter/shell/platform/darwin/common:test_config", "//flutter/shell/platform/darwin/common:swift_testing_config", ] - sources = [ "framework/Source/ResizeSynchronizerTest.swift" ] + sources = [ "framework/Source/ResizeSynchronizerTests.swift" ] deps = [ ":flutter_framework_source", "//flutter/shell/platform/darwin/common:swift_testing_main", diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTest.swift b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTests.swift similarity index 99% rename from engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTest.swift rename to engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTests.swift index 04cec96b24920..47fdfa9d31fa8 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTest.swift +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/ResizeSynchronizerTests.swift @@ -11,7 +11,7 @@ import Testing // in effect, a source of implicit shared state/behaviour. Because `beginResize` is a blocking call // performed on the main thread, we serialise to avoid potential interactions between tests. @Suite("ResizeSynchronizer tests", .serialized) -struct ResizeSynchronizerTest { +struct ResizeSynchronizerTests { @MainActor @Test("performCommit callback executes when no resize is active") From 2d3957cbfb85676acc4016ff91c76d3e7432825c Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Mon, 27 Jul 2026 19:10:24 -0700 Subject: [PATCH 068/147] Run Mac golden tests on ARM bots (#189465) Intel bots are being deprecated. Pin the builds that generate goldens to ARM instead of Intel (see https://github.com/flutter/flutter/issues/119880). This PR indeed [generated new goldens](https://flutter-gold.skia.org/search?crs=github&issue=189465&patchsets=2&untriaged=false) as described in https://github.com/flutter/flutter/issues/119880. - Mac framework_tests_libraries - Mac framework_tests_impeller - Mac framework_tests_widgets Fixes https://github.com/flutter/flutter/issues/119880 Part of https://github.com/flutter/flutter/issues/189144 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 4980770c69734..d8f3a738f017c 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -4258,7 +4258,7 @@ targets: recipe: flutter/flutter_drone timeout: 60 properties: - cpu: x86 # https://github.com/flutter/flutter/issues/119880 + cpu: arm64 # https://github.com/flutter/flutter/issues/119880 dependencies: >- [ {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4286,7 +4286,7 @@ targets: recipe: flutter/flutter_drone timeout: 60 properties: - cpu: x86 # https://github.com/flutter/flutter/issues/119880 + cpu: arm64 # https://github.com/flutter/flutter/issues/119880 dependencies: >- [ {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} @@ -4379,7 +4379,7 @@ targets: recipe: flutter/flutter_drone timeout: 60 properties: - cpu: x86 # https://github.com/flutter/flutter/issues/119880 + cpu: arm64 # https://github.com/flutter/flutter/issues/119880 dependencies: >- [ {"dependency": "goldctl", "version": "git_revision:c845c41b9b81bfcb11f2f0ab17b5b2386d634c31"} From 5aa364b65ed8c7f6d029981baa017e936bed1032 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 28 Jul 2026 12:14:35 +0900 Subject: [PATCH 069/147] iOS: Migrate TaskRunner tests to Swift Testing (#190055) These tests use a TaskRunner on the test main thread, which posts to the current thread's fml::MessageLoop, backed by its CFRunLoop. Under XCTest, waitForExpectations spins the run loop, which ensures the posted tasks run. Since Swift Testing has no direct equivalent this adds a `runLoopUntil` helper that runs `RunLoop.current` until the task completes or the timeout elapses, giving us the same behaviour. No semantic changes; this just migrates to Swift Testing. ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../framework/Source/TaskRunnerTests.swift | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/TaskRunnerTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/TaskRunnerTests.swift index 84882b2799ef0..9eae6d1329809 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/TaskRunnerTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/TaskRunnerTests.swift @@ -3,39 +3,45 @@ // found in the LICENSE file. import InternalFlutterSwift -import XCTest +import QuartzCore +import Testing -class TaskRunnerTests: XCTestCase { +// A current-thread `TaskRunner` posts to the current thread's `fml::MessageLoop`, which is backed by +// the thread's run loop. `@MainActor` binds the runner to the main thread, whose run loop the test +// host keeps running; each `await` yields to that run loop so posted tasks execute without manual +// pumping. +@MainActor +struct TaskRunnerTests { - func testPostTask() { + @Test func postTask() async { let taskRunner = TaskRunnerTestHelper.makeCurrentThreadTaskRunner() - let expectation = self.expectation(description: "Task should be executed") - taskRunner.postTask { - expectation.fulfill() + await withCheckedContinuation { continuation in + taskRunner.postTask { + continuation.resume() + } } - - waitForExpectations(timeout: 5.0, handler: nil) } - func testPostDelayedTask() { + @Test func postDelayedTask() async { let taskRunner = TaskRunnerTestHelper.makeCurrentThreadTaskRunner() - let expectation = self.expectation(description: "Delayed task should be executed") + var elapsed: CFTimeInterval = 0 let startTime = CACurrentMediaTime() - taskRunner.postTask(delay: 0.1) { - let endTime = CACurrentMediaTime() - let epsilon = 0.001 - XCTAssertGreaterThanOrEqual(endTime - startTime, 0.1 - epsilon) - expectation.fulfill() + await withCheckedContinuation { continuation in + taskRunner.postTask(delay: 0.1) { + elapsed = CACurrentMediaTime() - startTime + continuation.resume() + } } - waitForExpectations(timeout: 5.0, handler: nil) + let epsilon = 0.001 + #expect(elapsed >= 0.1 - epsilon) } - func testRunsTasksOnCurrentThread() { + @Test func runsTasksOnCurrentThread() { let taskRunner = TaskRunnerTestHelper.makeCurrentThreadTaskRunner() - XCTAssertTrue(taskRunner.runsTasksOnCurrentThread()) + #expect(taskRunner.runsTasksOnCurrentThread()) } } From 78703736057924baeb2b885dbfa2599c578e4f3d Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 28 Jul 2026 12:32:27 +0900 Subject: [PATCH 070/147] iOS: Reject merged-platform-ui-thread=mergeAfterLaunch (#190051) iOS runs with the platform and UI threads merged from launch and only implements fully-merged (`kEnabled`) threading. The `mergeAfterLaunch` approach starts the engine on a separate UI thread and merges it onto the platform thread once the root isolate is running. The `mergeAfterLaunch` merge/unmerge code lives entirely in `shell/common`. The iOS embedder has never implemented it, and no iOS code has ever referenced it. On iOS, the flag was never wired up, and it isn't reachable through any supported configuration. The `FLTEnableMergedPlatformUIThread` Info.plist key only ever selects `kEnabled` or `kDisabled`. The only way to reach it is the raw command-line flag, which nothing rejects today, so passing `--merged-platform-ui-thread=mergeAfterLaunch` slips through and produces a broken engine since: * `Shell::Spawn` refuses `kMergeAfterLaunch` and returns null, so `FlutterEngineGroup` fails to spawn secondary engines outright. * The CADisplayLink-backed vsync waiter registers on the run loop of whichever thread services the UI task runner when it's constructed. Under the merged model iOS actually ships (`kEnabled`) that's the platform thread, which is correct. Under `mergeAfterLaunch` it would be the temporary UI thread that the model abandons after launch, and which won't work. This has been unsupported since `mergeAfterLaunch` was introduced. Since every iOS `Settings` setting passes through `FlutterDartProject`, we can fail startup there with an `FML_CHECK` if this configuration is set. Normally this would live in `shell/common/switches.cc` but Android also passes `require_merged_platform_ui_thread` to `SettingsFromCommandLine` and still supports `mergeAfterLaunch` as a launch-latency optimization used by internal customers. Also added a test that hints that `mergeAfterLaunch` is an intentional, supported configuration so that people like me don't try to lock it down again. See: https://github.com/flutter/flutter/pull/189893 This is part of cleanup work intended to simplify the iOS embedder prior to an eventual migration to the embedder API. Issue: https://github.com/flutter/flutter/issues/112232 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../src/flutter/shell/common/switches_unittests.cc | 13 +++++++++++++ .../ios/framework/Source/FlutterDartProject.mm | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/engine/src/flutter/shell/common/switches_unittests.cc b/engine/src/flutter/shell/common/switches_unittests.cc index 8042c3a59bb03..9c89905f2dd2b 100644 --- a/engine/src/flutter/shell/common/switches_unittests.cc +++ b/engine/src/flutter/shell/common/switches_unittests.cc @@ -161,6 +161,19 @@ TEST(SwitchesTest, RequireMergedPlatformUIThread) { "This platform does not support the " "merged-platform-ui-thread=disabled flag"); } + +// Ensure mergeAfterLaunch is passed correctly. +// +// This is a supported threading model even on some platforms (e.g. Android) +// that enforce a merged platform/UI thread. For embedders where this isn't a +// supported behavior, it can be blocked in the embedder itself. +TEST(SwitchesTest, RequireMergedPlatformUIThreadAllowsMergeAfterLaunch) { + fml::CommandLine command_line = fml::CommandLineFromInitializerList( + {"command", "--merged-platform-ui-thread=mergeAfterLaunch"}); + Settings settings = SettingsFromCommandLine(command_line, true); + EXPECT_EQ(settings.merged_platform_ui_thread, + Settings::MergedPlatformUIThread::kMergeAfterLaunch); +} #endif // !OS_FUCHSIA } // namespace testing diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 7f5b83ca59892..2ed1a6ec4a3b6 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -67,6 +67,10 @@ static BOOL DoesHardwareSupportWideGamut() { auto settings = flutter::SettingsFromCommandLine(command_line, true); + FML_CHECK(settings.merged_platform_ui_thread != + flutter::Settings::MergedPlatformUIThread::kMergeAfterLaunch) + << "merged-platform-ui-thread=mergeAfterLaunch is not supported on iOS."; + settings.task_observer_add = [](intptr_t key, const fml::closure& callback) { fml::TaskQueueId queue_id = fml::MessageLoop::GetCurrentTaskQueueId(); fml::MessageLoopTaskQueues::GetInstance()->AddTaskObserver(queue_id, key, callback); From 0f0246377b1c9d8bc365a439c520a7de6c3f590b Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 06:03:41 -0400 Subject: [PATCH 071/147] Roll Fuchsia Test Scripts from E8hJ1AfK8CtGtaES0... to 1frGe_KltAJKkeyPg... (#190134) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-test-scripts-flutter Please CC aaclarke@google.com,chrome-fuchsia-engprod@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 166884a3ea282..1c45d752f3216 100644 --- a/DEPS +++ b/DEPS @@ -201,7 +201,7 @@ vars = { # The version / instance id of the cipd:chromium/fuchsia/test-scripts which # will be used altogether with fuchsia-sdk to setup the build / test # environment. - 'fuchsia_test_scripts_version': 'E8hJ1AfK8CtGtaES0CR9ne88d3fHsWBF9Bv7Icu38k4C', + 'fuchsia_test_scripts_version': '1frGe_KltAJKkeyPgy4cDJqScCYVYSpC9sJfjflcvl4C', # The version / instance id of the cipd:chromium/fuchsia/gn-sdk which will be # used altogether with fuchsia-sdk to generate gn based build rules. From d6d999e765822756b6e13679b7ac03e88dcf1371 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 11:32:14 -0400 Subject: [PATCH 072/147] Roll Packages from 69693296b8df to 3e636359fb00 (6 revisions) (#190142) https://github.com/flutter/packages/compare/69693296b8df...3e636359fb00 2026-07-28 269567208+reidbaker-agent@users.noreply.github.com [camera_android_camerax] Add the ability to unit test skills. (flutter/packages#12285) 2026-07-28 stuartmorgan@google.com [ci] Tag dependabot PRs with `triage-android` (flutter/packages#12142) 2026-07-26 engine-flutter-autoroll@skia.org Roll Flutter from e2921977d4cc to 99889603182d (1 revision) (flutter/packages#12288) 2026-07-25 engine-flutter-autoroll@skia.org Roll Flutter from b65f4d9bc3fe to e2921977d4cc (19 revisions) (flutter/packages#12287) 2026-07-24 engine-flutter-autoroll@skia.org Roll Flutter from 2a2a79d36624 to b65f4d9bc3fe (30 revisions) (flutter/packages#12284) 2026-07-24 engine-flutter-autoroll@skia.org Roll Flutter (stable) from 84fc5cbb223b to 058e0af2c2b5 (6 revisions) (flutter/packages#12282) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 9020a141f5534..f49bb03e178fe 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -69693296b8df2c5068efb45de6c6a963d61163a1 +3e636359fb007e63909fd4b656b07c7d4da3133a From 6bfd8f4d381163edda287984adc2811e32d1c285 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:42:21 -0700 Subject: [PATCH 073/147] Made linter more robust to non-utf8 files (#190012) Fixing a problem a ran into locally. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../flutter/tools/clang_tidy/lib/src/command.dart | 4 ++-- .../tools/clang_tidy/test/clang_tidy_test.dart | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/tools/clang_tidy/lib/src/command.dart b/engine/src/flutter/tools/clang_tidy/lib/src/command.dart index a37196562de37..fb608315a510a 100644 --- a/engine/src/flutter/tools/clang_tidy/lib/src/command.dart +++ b/engine/src/flutter/tools/clang_tidy/lib/src/command.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:convert' show LineSplitter, utf8; +import 'dart:convert' show LineSplitter, Utf8Decoder; import 'dart:io' as io; import 'package:meta/meta.dart'; @@ -111,7 +111,7 @@ class Command { } final Stream lines = file .openRead() - .transform(utf8.decoder) + .transform(const Utf8Decoder(allowMalformed: true)) .transform(const LineSplitter()); return lintActionFromContents(lines); } diff --git a/engine/src/flutter/tools/clang_tidy/test/clang_tidy_test.dart b/engine/src/flutter/tools/clang_tidy/test/clang_tidy_test.dart index 4889df3266c1f..c767363cbed26 100644 --- a/engine/src/flutter/tools/clang_tidy/test/clang_tidy_test.dart +++ b/engine/src/flutter/tools/clang_tidy/test/clang_tidy_test.dart @@ -532,6 +532,18 @@ void main() { expect(lintAction, equals(LintAction.skipMissing)); }); + test('Command getLintAction handles non-UTF8 files without throwing', () async { + final io.Directory tempDir = io.Directory.systemTemp.createTempSync('clang_tidy_test_'); + final tempFile = io.File(path.join(tempDir.path, 'non_utf8_file.cc')); + tempFile.writeAsBytesSync([0x80, 0x81, 0xFE, 0xFF, 0x0A]); + try { + final LintAction lintAction = await Command.getLintAction(tempFile.path); + expect(lintAction, equals(LintAction.lint)); + } finally { + tempDir.deleteSync(recursive: true); + } + }); + test('Command getLintActionFromContents flags FLUTTER_NOLINT', () async { final LintAction lintAction = await Command.lintActionFromContents( Stream.fromIterable([ From d063bd83f71608aa257f6c0fb7906ce5e9e42720 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 15:02:17 -0400 Subject: [PATCH 074/147] Roll Skia from 62442d6cf0ec to d78865e708ad (30 revisions) (#190152) https://skia.googlesource.com/skia.git/+log/62442d6cf0ec..d78865e708ad 2026-07-28 kjlubick@google.com Use hermetic Python directly from dawn_repo.bzl 2026-07-28 kjlubick@google.com Add missing GN deps on shaper 2026-07-28 borenet@google.com [infra] Remove all remaining references to gsutil 2026-07-28 kjlubick@google.com [bazel] Add Windows CI job for building viewer 2026-07-28 chrome-branch-day@chops-service-accounts.iam.gserviceaccount.com Merge 4 release notes into RELEASE_NOTES.md 2026-07-28 chrome-branch-day@chops-service-accounts.iam.gserviceaccount.com Update Skia milestone to 153 2026-07-28 jianlin.qiu@intel.com Ganesh: honor subset when flattening a YUVA image to a special image 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Manual roll Skia Infra from d47334d0785d to 42f146e4e61d (40 revisions) 2026-07-28 borenet@google.com [recipes] Switch from "gsutil" to "gcloud storage" 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from b97f97b70455 to 0d9c229af89c (6 revisions) 2026-07-28 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-28 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 1e897275172a to 10934785b43b (18 revisions) 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from c3ede28106e9 to ff4855759c68 (11 revisions) 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from b4889b3141e3 to a46a65d7ba05 2026-07-27 ndesaulniers@google.com Add missing header to FrontBufferedStream.cpp 2026-07-27 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-27 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 70a303980973 to b97f97b70455 (5 revisions) 2026-07-27 arthursonzogni@chromium.org miracleptr: Add RAW_PTR_EXCLUSION for pointers in stable C ABIs 2026-07-27 wenting_shi@apple.com [skottie] Add support for Skottie ColorOverlay style 2026-07-27 thomsmit@google.com [graphite] SparseStrips approximate conics with cubics 2026-07-27 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-27 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 92de898b7a32 to c3ede28106e9 (3 revisions) 2026-07-27 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from f147dcdbea36 to 70a303980973 (2 revisions) 2026-07-27 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from a56ab87a19a0 to 1e897275172a (16 revisions) 2026-07-27 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from 773cb2ff3ea5 to b4889b3141e3 2026-07-27 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-26 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 59e03a8479a3 to f147dcdbea36 (1 revision) 2026-07-26 skia-autoroll@skia-public.iam.gserviceaccount.com Roll SKP CIPD package from 569 to 570 2026-07-25 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 8e393ad533ce to 59e03a8479a3 (1 revision) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- engine/src/flutter/sky/packages/sky_engine/LICENSE | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1c45d752f3216..7da095c7b5bd3 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '62442d6cf0ec21e3e7f1c23b018c619374fa39c7', + 'skia_revision': 'd78865e708ad437e46dcfc382ebd39e00dcc78c1', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds diff --git a/engine/src/flutter/sky/packages/sky_engine/LICENSE b/engine/src/flutter/sky/packages/sky_engine/LICENSE index c6d60bce87eba..83ba711a25372 100644 --- a/engine/src/flutter/sky/packages/sky_engine/LICENSE +++ b/engine/src/flutter/sky/packages/sky_engine/LICENSE @@ -18110,6 +18110,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- +skia + +Copyright 2026 Apple Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- glslang Copyright 2026 Google LLC From 99a98f13425921c8339a4f8d438c0130fe2df355 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 29 Jul 2026 06:16:35 +0900 Subject: [PATCH 075/147] iOS: Fix use-after-free race during shell teardown (#190132) -[FlutterEngine waitForFirstFrame:callback:] dispatches a block to a QOS_CLASS_BACKGROUND queue that dereferences the `_shell` unique_ptr and blocks in `Shell::WaitForFirstFrame()` on Shell-owned `waiting_for_first_frame_mutex_`/`waiting_for_first_frame_condition_`. Previously, nothing prevented `-destroyContext` from running concurrently on the main thread and calling `_shell.reset()` while that background block was still running, or hadn't started yet. `Shell::~Shell` only joins the platform/UI/raster/IO task runners. It has no idea a separate GCD thread might be parked (or about to park) inside the object, so `_shell.reset()` can free the Shell out from under the block, which then reads and locks/unlocks freed memory. Specifically: * We dispatch the background block in `-[FlutterEngine waitForFirstFrame:callback:]`, which captures `weakSelf`. * Before the block runs, or while it's blocked on the condition variable, the main thread calls `destroyContext`, which resets `_shell`. * The block resolves `strongSelf` which is still valid -- that's the FlutterEngine wrapper, not `_shell`, and calls `strongSelf.shell.WaitForFirstFrame(...)` on a Shell that's already been freed. This is reachable via -sendDeepLinkToFramework:completionHandler:, which calls waitForFirstFrame: with a 3s timeout, racing any concurrent lifecycle teardown (scene disconnect, view controller dealloc). This adds `Shell::CancelWaitForFirstFrame()`, which flips a cancelled flag under `waiting_for_first_frame_mutex_` and notifies the condition var, causing any current/upcoming `WaitForFirstFrame()` call to return `kAborted` promptly instead of blocking out its timeout. We call this before resetting the shell in `-destroyContext`, then wait on a dispatch_group for that block to actually finish touching `_shell` before freeing it. This is a separate dispatch_group to the one that already blocks the completion callback, so that concurrent waitForFirstFrame: calls don't have their callbacks coupled to each other. The unit tests was copied (near-)verbatim from repro on the bug. Issue: b/521830222 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- engine/src/flutter/shell/common/shell.cc | 18 +++++- engine/src/flutter/shell/common/shell.h | 27 ++++++++ .../flutter/shell/common/shell_unittests.cc | 62 +++++++++++++++++++ .../ios/framework/Source/FlutterEngine.mm | 24 ++++++- 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index 2200280ecc4da..a4414c6cb0953 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -2402,16 +2402,28 @@ fml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) { std::unique_lock lock(waiting_for_first_frame_mutex_); bool success = waiting_for_first_frame_condition_.wait_until( - lock, duration, [&waiting_for_first_frame = waiting_for_first_frame_] { - return !waiting_for_first_frame.load(); + lock, duration, + [&waiting_for_first_frame = waiting_for_first_frame_, + &cancelled = wait_for_first_frame_cancelled_] { + return !waiting_for_first_frame.load() || cancelled; }); - if (success) { + if (wait_for_first_frame_cancelled_) { + return fml::Status(fml::StatusCode::kAborted, "Shell is shutting down."); + } else if (success) { return fml::Status(); } else { return fml::Status(fml::StatusCode::kDeadlineExceeded, "timeout"); } } +void Shell::CancelWaitForFirstFrame() { + { + std::scoped_lock lock(waiting_for_first_frame_mutex_); + wait_for_first_frame_cancelled_ = true; + } + waiting_for_first_frame_condition_.notify_all(); +} + bool Shell::ReloadSystemFonts() { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index 0eb7248946e99..bc845af795516 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -352,6 +352,21 @@ class Shell final : public PlatformView::Delegate, /// fml::Status WaitForFirstFrame(fml::TimeDelta timeout); + //---------------------------------------------------------------------------- + /// @brief Unblocks any call to WaitForFirstFrame(), causing it to + /// immediately return 'kAborted' instead of blocking for the + /// full timeout. + /// + /// Embedders that pass a reference to the Shell to a thread they + /// do not otherwise synchronize with the shell's destruction + /// must call this, and wait for that thread to finish with the + /// shell, before destroying it. This method only prevents + /// WaitForFirstFrame() from blocking; it does not by itself + /// make it safe to destroy the Shell out from under a caller + /// that has not yet returned from WaitForFirstFrame(). + /// + void CancelWaitForFirstFrame(); + //---------------------------------------------------------------------------- /// @brief Used by embedders to reload the system fonts in /// FontCollection. @@ -507,7 +522,19 @@ class Shell final : public PlatformView::Delegate, uint64_t next_pointer_flow_id_ = 0; bool first_frame_rasterized_ = false; + + // True if a first frame has not yet been rendered. + // + // This is read and written lock-free on the raster thread, and read under + // waiting_for_first_frame_mutex_ in WaitForFirstFrame. std::atomic waiting_for_first_frame_ = true; + + // True when WaitForFirstFrame has been cancelled because the shell is + // shutting down and waiting threads should be unblocked. + // + // Guarded by waiting_for_first_frame_mutex_. + bool wait_for_first_frame_cancelled_ = false; + std::mutex waiting_for_first_frame_mutex_; std::condition_variable waiting_for_first_frame_condition_; diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 56ca523fd0f50..110b4ccbe9640 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -1728,6 +1728,68 @@ TEST_F(ShellTest, WaitForFirstFrameTimeout) { DestroyShell(std::move(shell)); } +// Ensure CancelWaitForFirstFrame() correctly causes all tasks blocked on +// WaitForFirstFrame() to return kAborted. +// +// See: b/521830222 +TEST_F(ShellTest, CancelWaitForFirstFrameAllowsSafeShellDestruction) { + auto settings = CreateSettingsForFixture(); + std::unique_ptr shell = CreateShell(settings); + + PlatformViewNotifyCreated(shell.get()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("emptyMain"); + RunEngine(shell.get(), std::move(configuration)); + // No PumpOneFrame: waiting_for_first_frame_ stays true, so + // WaitForFirstFrame would otherwise park on the condvar for the full + // timeout below. + + fml::AutoResetWaitableEvent bg_has_ref; + fml::AutoResetWaitableEvent proceed_with_wait; + + // Background thread holds a raw Shell* obtained while the shell was still + // live -- exactly what `strongSelf.shell` (-> `*_shell`) hands the GCD + // block in -[FlutterEngine waitForFirstFrame:callback:]. + Shell* raw_shell = shell.get(); + fml::Status background_result; + std::thread background( + [raw_shell, &bg_has_ref, &proceed_with_wait, &background_result] { + bg_has_ref.Signal(); + proceed_with_wait.Wait(); + // A well-behaved caller must not still be here once the owner has + // finished destroying the Shell. CancelWaitForFirstFrame() below makes + // sure this call returns promptly instead of blocking for 30 seconds. + background_result = + raw_shell->WaitForFirstFrame(fml::TimeDelta::FromSeconds(30)); + }); + + bg_has_ref.Wait(); + proceed_with_wait.Signal(); + + // Give the background thread a chance to actually call WaitForFirstFrame() + // before it is cancelled below. If it hasn't gotten there yet, cancellation + // is still observed safely (and just as fast) the moment it does. + std::this_thread::yield(); + + fml::TimePoint cancel_start = fml::TimePoint::Now(); + // Models -[FlutterEngine destroyContext]: cancel any in-flight waiter, + // then join it, before freeing the Shell. + raw_shell->CancelWaitForFirstFrame(); + background.join(); + fml::TimeDelta elapsed = fml::TimePoint::Now() - cancel_start; + + // The whole point of CancelWaitForFirstFrame() is to avoid blocking the + // owner for anywhere near the caller's requested timeout. + EXPECT_LT(elapsed.ToSecondsF(), 5.0); + ASSERT_FALSE(background_result.ok()); + ASSERT_EQ(background_result.code(), fml::StatusCode::kAborted); + + // Only safe to destroy now that the background thread has been joined, + // i.e. is guaranteed to no longer be touching the Shell. + DestroyShell(std::move(shell)); +} + TEST_F(ShellTest, WaitForFirstFrameMultiple) { auto settings = CreateSettingsForFixture(); std::unique_ptr shell = CreateShell(settings); diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 1bf55855a7ff8..b07f3c32c4844 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -196,6 +196,10 @@ @implementation FlutterEngine { std::shared_ptr _threadHost; std::unique_ptr _shell; + // Callers to -waitForFirstFrame:callback: that are currently queued/processing. + // -destroyContext must wait for this group to drain before it is safe to free _shell. + dispatch_group_t _firstFrameWaiters; + flutter::IOSRenderingAPI _renderingApi; std::shared_ptr _profiler; @@ -571,6 +575,13 @@ - (void)notifyViewControllerDeallocated { } - (void)destroyContext { + // Clear any tasks waiting on first frame prior to destroying _shell. + if (_shell) { + _shell->CancelWaitForFirstFrame(); + } + if (_firstFrameWaiters) { + dispatch_group_wait(_firstFrameWaiters, DISPATCH_TIME_FOREVER); + } [self resetChannels]; self.isolateId = nil; _shell.reset(); @@ -1535,17 +1546,28 @@ - (void)waitForFirstFrame:(NSTimeInterval)timeout dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0); dispatch_group_t group = dispatch_group_create(); + // Increment count of tasks waiting for first frame callback. Decrement below + // on completion or timeout. In -destroyContext we block until all pending + // first frame waiter tasks are cancelled. + if (!_firstFrameWaiters) { + _firstFrameWaiters = dispatch_group_create(); + } + dispatch_group_t firstFrameWaiters = _firstFrameWaiters; + dispatch_group_enter(firstFrameWaiters); + __weak FlutterEngine* weakSelf = self; __block BOOL didTimeout = NO; dispatch_group_async(group, queue, ^{ FlutterEngine* strongSelf = weakSelf; - if (!strongSelf) { + if (!strongSelf || !strongSelf->_shell) { + dispatch_group_leave(firstFrameWaiters); return; } fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000); fml::Status status = strongSelf.shell.WaitForFirstFrame(waitTime); didTimeout = status.code() == fml::StatusCode::kDeadlineExceeded; + dispatch_group_leave(firstFrameWaiters); }); // Only execute the main queue task once the background task has completely finished executing. From d2e37546701ea4e80447ffcda148335e8463d056 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 20:06:01 -0400 Subject: [PATCH 076/147] Roll Skia from d78865e708ad to 70733f74d415 (2 revisions) (#190168) https://skia.googlesource.com/skia.git/+log/d78865e708ad..70733f74d415 2026-07-28 michaelludwig@google.com [graphite] Start refactoring Layer::test for more early-out conditions 2026-07-28 borenet@google.com Revert "[infra] Remove all remaining references to gsutil" If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7da095c7b5bd3..0a004917a8e7c 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'd78865e708ad437e46dcfc382ebd39e00dcc78c1', + 'skia_revision': '70733f74d415bf389f6bda812e95e8aee6182664', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 365616426cc5a5c90548825b8f6c68d0d2d2c30c Mon Sep 17 00:00:00 2001 From: Bruno Corona Date: Tue, 28 Jul 2026 18:06:01 -0600 Subject: [PATCH 077/147] Scope SemanticsTester per test in scrollable_semantics_test (#189800) The test file used a single top-level `SemanticsTester semantics;` shared across all tests. That is an anti-pattern: state can bleed between tests if one fails to dispose it. This scopes the `SemanticsTester` locally within each test instead, removing the shared top-level variable. No behavior change to the tests themselves. Fixes #189799 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. --- .../widgets/scrollable_semantics_test.dart | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/flutter/test/widgets/scrollable_semantics_test.dart b/packages/flutter/test/widgets/scrollable_semantics_test.dart index 3617b7119176a..0bf5c643835da 100644 --- a/packages/flutter/test/widgets/scrollable_semantics_test.dart +++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart @@ -35,14 +35,12 @@ class _PinnedHeaderDelegate extends SliverPersistentHeaderDelegate { const double _kToolbarHeight = 56.0; void main() { - SemanticsTester semantics; - setUp(() { debugResetSemanticsIdCounter(); }); testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -101,7 +99,7 @@ void main() { }); testWidgets('Vertical scrollable responds to scrollToOffset', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); final controller = ScrollController(); await tester.pumpWidget( Directionality( @@ -132,7 +130,7 @@ void main() { }); testWidgets('Horizontal scrollable responds to scrollToOffset', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); final controller = ScrollController(); await tester.pumpWidget( Directionality( @@ -166,7 +164,7 @@ void main() { testWidgets('Unscrollable scrollable does not respond to scrollToOffset', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -185,7 +183,7 @@ void main() { testWidgets('Scrollable exposes implicit scrolling before dimensions are available', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); final controller = _NoDimensionsDuringSemanticsScrollController(); addTearDown(controller.dispose); @@ -212,7 +210,7 @@ void main() { testWidgets('scrollToOffset respects implicit scrolling configuration', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); final ScrollPhysics physics = _NoImplicitScrollingScrollPhysics(); await tester.pumpWidget( Directionality( @@ -233,7 +231,7 @@ void main() { }); testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation const kItemHeight = 40.0; @@ -280,7 +278,7 @@ void main() { testWidgets('showOnScreen works with pinned app bar and sliver list', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation const kItemHeight = 100.0; const kExpandedAppBarHeight = 56.0; @@ -343,7 +341,7 @@ void main() { testWidgets('showOnScreen works with pinned app bar and individual slivers', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation const kItemHeight = 100.0; const kExpandedAppBarHeight = 256.0; @@ -401,7 +399,7 @@ void main() { }); testWidgets('correct scrollProgress', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); await tester.pumpWidget( Directionality( @@ -452,7 +450,7 @@ void main() { }); testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); await tester.pumpWidget( Directionality( @@ -513,7 +511,7 @@ void main() { }); testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async { - semantics = SemanticsTester(tester); + final semantics = SemanticsTester(tester); final children = List.generate( 80, @@ -571,7 +569,7 @@ void main() { expect(tester.binding.pipelineOwner.semanticsOwner, isNull); // Semantics on - semantics = SemanticsTester(tester); + var semantics = SemanticsTester(tester); await tester.pumpAndSettle(); expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull); expect( @@ -624,7 +622,7 @@ void main() { }); testWidgets('brings item above leading edge to leading edge', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -646,7 +644,7 @@ void main() { }); testWidgets('brings item below trailing edge to trailing edge', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -670,7 +668,7 @@ void main() { testWidgets('does not change position of items already fully on-screen', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -747,7 +745,7 @@ void main() { }); testWidgets('brings item above leading edge to leading edge', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -769,7 +767,7 @@ void main() { }); testWidgets('brings item below trailing edge to trailing edge', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -793,7 +791,7 @@ void main() { testWidgets('does not change position of items already fully on-screen', ( WidgetTester tester, ) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation await tester.pumpWidget(widgetUnderTest); @@ -818,7 +816,7 @@ void main() { testWidgets( 'transform of inner node from useTwoPaneSemantics scrolls correctly with nested scrollables', (WidgetTester tester) async { - semantics = SemanticsTester(tester); // enables semantics tree generation + final semantics = SemanticsTester(tester); // enables semantics tree generation // Context: https://github.com/flutter/flutter/issues/61631 await tester.pumpWidget( From c83f80b2367baf0560fd9c73553a10b2fc51df5b Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Tue, 28 Jul 2026 17:07:33 -0700 Subject: [PATCH 078/147] Use super-parameter in two_dimensional_utils (#189957) Work towards https://github.com/dart-lang/sdk/issues/59226 We are reporting some missed cases of 'use_super_parameters'. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter/test/widgets/two_dimensional_utils.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter/test/widgets/two_dimensional_utils.dart b/packages/flutter/test/widgets/two_dimensional_utils.dart index d09554ea25fc7..26296362c01de 100644 --- a/packages/flutter/test/widgets/two_dimensional_utils.dart +++ b/packages/flutter/test/widgets/two_dimensional_utils.dart @@ -341,14 +341,14 @@ class SimpleListTableView extends TwoDimensionalScrollView { super.mainAxis = Axis.vertical, super.verticalDetails = const ScrollableDetails.vertical(), super.horizontalDetails = const ScrollableDetails.horizontal(), - required TwoDimensionalChildListDelegate delegate, + required super.delegate, super.cacheExtent, super.cacheExtentStyle, super.diagonalDragBehavior = DiagonalDragBehavior.none, super.dragStartBehavior = DragStartBehavior.start, super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, super.clipBehavior = Clip.hardEdge, - }) : super(delegate: delegate); + }); @override Widget buildViewport( From 5631ff3e9c42b4f52604fced587a7de57fb441c4 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen <1961493+harryterkelsen@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:07:33 -0700 Subject: [PATCH 079/147] [web] Provide Content-Length header for CanvasKit files in flutter test (#190155) This adds the `Content-Length` header when serving CanvasKit files in the `shelf` test server. This prevents `shelf` from falling back to `Transfer-Encoding: chunked`, which triggers a bug in Chrome 145's WebAssembly compilation that causes random test hangs. This builds upon PR #189623 which partially mitigated the issue via caching. Fixes https://github.com/flutter/flutter/issues/189275 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter_tools/lib/src/test/flutter_web_platform.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 0049e37ff91fd..5a31e32024443 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -542,6 +542,7 @@ window.\$dartLoader.loader.nextAttempt(); headers: { HttpHeaders.contentTypeHeader: contentType, HttpHeaders.cacheControlHeader: 'public, max-age=3600', + HttpHeaders.contentLengthHeader: canvasKitFile.lengthSync().toString(), }, ); } From 1b19b36e85baea6cfd25cc597318ee11db979c84 Mon Sep 17 00:00:00 2001 From: Felipe Morschel <52160996+FMorschel@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:09:04 -0300 Subject: [PATCH 080/147] Adds missing await on `instantiateImageCodecFromBuffer` (#188910) This is merely a work towards: - https://github.com/dart-lang/sdk/issues/62555. We are now making the new `analyzer` warning trigger on non-`async` cases too because that is probably what the code is _meant_ to do. I don't expect this change to break any tests. If it does, we caught an untracked error. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md Co-authored-by: zhongliugo --- engine/src/flutter/lib/web_ui/lib/painting.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/painting.dart b/engine/src/flutter/lib/web_ui/lib/painting.dart index 65b56d2758d6d..73f133e97fa75 100644 --- a/engine/src/flutter/lib/web_ui/lib/painting.dart +++ b/engine/src/flutter/lib/web_ui/lib/painting.dart @@ -741,9 +741,9 @@ Future instantiateImageCodecFromBuffer( int? targetWidth, int? targetHeight, bool allowUpscaling = true, -}) { +}) async { try { - return engine.renderer.instantiateImageCodec( + return await engine.renderer.instantiateImageCodec( buffer._list!, targetWidth: targetWidth, targetHeight: targetHeight, From e26a93d9a4f780bf377c97bbfe5d426111a4d614 Mon Sep 17 00:00:00 2001 From: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Date: Wed, 29 Jul 2026 08:11:55 +0800 Subject: [PATCH 081/147] Remove no shuffle from flutter_driver extension_test.dart and mock flutter.process channel (#187559) Part of https://github.com/flutter/flutter/issues/85160 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../test/src/real_tests/extension_test.dart | 106 +++++++++++++++--- 1 file changed, 89 insertions(+), 17 deletions(-) diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart index efbee43578bd0..10a4c3d64f974 100644 --- a/packages/flutter_driver/test/src/real_tests/extension_test.dart +++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart @@ -2,13 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(gspencergoog): Remove this tag once this test's state leaks/test -// dependencies have been fixed. -// https://github.com/flutter/flutter/issues/85160 -// Fails with "flutter test --test-randomize-ordering-seed=20210721" -@Tags(['no-shuffle']) -library; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide TextInputAction; import 'package:flutter/rendering.dart'; @@ -34,6 +27,21 @@ Future silenceDriverLogger(AsyncCallback callback) async { } void main() { + setUpAll(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + (MethodCall methodCall) async { + return null; + }, + ); + }); + + tearDownAll(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ); + }); group('waitUntilNoTransientCallbacks', () { late FlutterDriverExtension driverExtension; Map? result; @@ -308,6 +316,9 @@ void main() { () => jsonMessage.encodeMessage(['hello world'])!, ); }); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel', null); + }); // ignore: unawaited_futures channel.invokeMethod('sayHello', 'hello'); @@ -355,6 +366,10 @@ void main() { () => jsonMessage.encodeMessage(['hello world'])!, ); }); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel1', null); + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel2', null); + }); // ignore: unawaited_futures channel1.invokeMethod('sayHello', 'hello'); // ignore: unawaited_futures @@ -408,6 +423,10 @@ void main() { () => jsonMessage.encodeMessage(['hello world'])!, ); }); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel1', null); + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel2', null); + }); // ignore: unawaited_futures channel1.invokeMethod('sayHello', 'hello'); @@ -450,7 +469,7 @@ void main() { ) { return Future.delayed( const Duration(milliseconds: 20), - () => jsonMessage.encodeMessage(['hello world'])!, + () => jsonMessage.encodeMessage(const ['hello world'])!, ); }); @@ -461,10 +480,15 @@ void main() { ) { return Future.delayed( const Duration(milliseconds: 10), - () => jsonMessage.encodeMessage(['hello world'])!, + () => jsonMessage.encodeMessage(const ['hello world'])!, ); }); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel1', null); + tester.binding.defaultBinaryMessenger.setMockMessageHandler('helloChannel2', null); + }); + // ignore: unawaited_futures channel1.invokeMethod('sayHello', 'hello'); @@ -490,7 +514,7 @@ void main() { // Now we receive the result. await tester.pump(const Duration(milliseconds: 5)); - expect(result, {'isError': false, 'response': {}}); + expect(result, const {'isError': false, 'response': {}}); }, ); }); @@ -581,6 +605,16 @@ void main() { }); testWidgets('getText', (WidgetTester tester) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + (_) async => null, + ); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + null, + ); + }); await silenceDriverLogger(() async { final driverExtension = FlutterDriverExtension((String? arg) async => '', true, true); @@ -596,6 +630,16 @@ void main() { return GetTextResult.fromJson(result['response'] as Map).text; } + final controller3 = TextEditingController(text: 'Hello3'); + final controller4 = TextEditingController(text: 'Hello4'); + final controller5 = TextEditingController(text: 'Hello5'); + final focusNode3 = FocusNode(); + addTearDown(() { + controller3.dispose(); + controller4.dispose(); + controller5.dispose(); + focusNode3.dispose(); + }); await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -614,8 +658,8 @@ void main() { height: 25.0, child: EditableText( key: const ValueKey('text3'), - controller: TextEditingController(text: 'Hello3'), - focusNode: FocusNode(), + controller: controller3, + focusNode: focusNode3, style: const TextStyle(), cursorColor: Colors.red, backgroundCursorColor: Colors.black, @@ -623,16 +667,13 @@ void main() { ), SizedBox( height: 25.0, - child: TextField( - key: const ValueKey('text4'), - controller: TextEditingController(text: 'Hello4'), - ), + child: TextField(key: const ValueKey('text4'), controller: controller4), ), SizedBox( height: 25.0, child: TextFormField( key: const ValueKey('text5'), - controller: TextEditingController(text: 'Hello5'), + controller: controller5, ), ), SizedBox( @@ -975,6 +1016,16 @@ void main() { ); testWidgets('enableTextEntryEmulation false', (WidgetTester tester) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + (_) async => null, + ); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + null, + ); + }); driverExtension = FlutterDriverExtension((String? arg) async => '', true, false); await tester.pumpWidget(testWidget); @@ -984,6 +1035,16 @@ void main() { }); testWidgets('enableTextEntryEmulation true', (WidgetTester tester) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + (_) async => null, + ); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + null, + ); + }); driverExtension = FlutterDriverExtension((String? arg) async => '', true, true); await tester.pumpWidget(testWidget); @@ -1335,9 +1396,20 @@ void main() { ); testWidgets('press done trigger onSubmitted and change value', (WidgetTester tester) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + (_) async => null, + ); + addTearDown(() { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.processText, + null, + ); + }); driverExtension = FlutterDriverExtension((String? arg) async => '', true, true); final controller = TextEditingController(text: 'foo'); + addTearDown(controller.dispose); await tester.pumpWidget(testWidget(controller)); expect(controller.value.text, 'foo'); From 83c28004460586b54281185896995da882e3f338 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Wed, 29 Jul 2026 09:50:56 +0900 Subject: [PATCH 082/147] iOS: Migrate VSyncClient tests to Swift Testing (#190054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swift Testing has no equivalent of XCTest's expectation / waitForExpectations, so the object-lifetime tests have have been updated to use DispatchSemaphore, which is what we do in ResizeSynchronizerTest and what most closely resembles the previous code, plus provides the wait semantics and thread synchronisation we need. `confirmation()`/`confirm()` can't be used since it provides no wait/signal semantics. `confirmation` returns as soon as its block returns (whether or not `confirm` was called). Ref: [Validate asynchronous behaviors](https://developer.apple.com/documentation/testing/migratingfromxctest#Validate-asynchronous-behaviors) section. `withCheckedContinuation` can't be used since its docs clearly state "you must invoke the continuation’s resume method exactly once", which isn't viable with a CADisplayLink vsync callback. Ref: [Discussion](https://developer.apple.com/documentation/swift/withcheckedcontinuation(function:_:)#discussion) section. No semantic changes; this is just migrating the tests. ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../framework/Source/VSyncClientTests.swift | 117 ++++++++---------- 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift index d468c23c4962a..1a60bb5b378ba 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift @@ -2,22 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import XCTest +import Testing @testable import InternalFlutterSwift -class VSyncClientTests: XCTestCase { - var threadTaskRunner: TaskRunner! - - override func setUp() { - super.setUp() - threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "VSyncClientTest") - } - - override func tearDown() { - threadTaskRunner = nil - super.tearDown() - } +@MainActor +struct VSyncClientTests { + let threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "VSyncClientTest") /// Verifies that the vsync client safely synthesizes a target timestamp when the display link's /// `targetTimestamp` is invalid (i.e. evaluates to 0.0). @@ -37,7 +28,7 @@ class VSyncClientTests: XCTestCase { /// This test passes a newly created, paused `CADisplayLink` (whose properties both evaluate to /// 0.0) and asserts that the client intercepts the invalid state and synthesizes a safe, positive /// next-frame target timestamp based on the display's maximum refresh rate. - func testRealDisplayLinkVsyncTimestampsCorrect() { + @Test func realDisplayLinkVsyncTimestampsCorrect() throws { var callbackStartTime: CFTimeInterval = -1 var callbackTargetTime: CFTimeInterval = -1 let vsyncClient = VSyncClient( @@ -48,18 +39,18 @@ class VSyncClientTests: XCTestCase { callbackStartTime = startTime callbackTargetTime = targetTime } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) vsyncClient.onDisplayLink(link) // Since the display link is paused and has not delivered a frame yet, both timestamp and // targetTimestamp are 0.0. Verify the client synthesizes a valid target timestamp using the max // refresh rate. - XCTAssertGreaterThan(callbackStartTime, 0.0) - XCTAssertEqual(callbackTargetTime - callbackStartTime, 1.0 / 60.0, accuracy: 0.0001) + #expect(callbackStartTime > 0.0) + #expect(abs((callbackTargetTime - callbackStartTime) - 1.0 / 60.0) <= 0.0001) } - func testVsyncClientPreventsZeroRefreshRateDivision() { + @Test func vsyncClientPreventsZeroRefreshRateDivision() throws { var callbackStartTime: CFTimeInterval = -1 var callbackTargetTime: CFTimeInterval = -1 // Initialize with maxRefreshRate = 0.0 to simulate uninitialized/zero max refresh rate. @@ -71,18 +62,18 @@ class VSyncClientTests: XCTestCase { callbackStartTime = startTime callbackTargetTime = targetTime } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) vsyncClient.onDisplayLink(link) - XCTAssertGreaterThan(callbackStartTime, 0.0) + #expect(callbackStartTime > 0.0) // Should fallback to effectiveRefreshRate of 60.0. - XCTAssertEqual(callbackTargetTime - callbackStartTime, 1.0 / 60.0, accuracy: 0.0001) - XCTAssertFalse(callbackTargetTime.isNaN) - XCTAssertFalse(callbackTargetTime.isInfinite) + #expect(abs((callbackTargetTime - callbackStartTime) - 1.0 / 60.0) <= 0.0001) + #expect(callbackTargetTime.isNaN == false) + #expect(callbackTargetTime.isInfinite == false) } - func testRefreshRatePropertyFallsBackToDefaultWhenInvalid() { + @Test func refreshRatePropertyFallsBackToDefaultWhenInvalid() { // Initialize with 0.0 to simulate invalid state. let vsyncClient = VSyncClient( taskRunner: threadTaskRunner, @@ -91,92 +82,90 @@ class VSyncClientTests: XCTestCase { ) { _, _ in } // Should return default rate (60.0). - XCTAssertEqual(vsyncClient.refreshRate, 60.0) + #expect(vsyncClient.refreshRate == 60.0) } - func testSetAllowPauseAfterVsyncCorrect() { + @Test func setAllowPauseAfterVsyncCorrect() throws { let vsyncClient = VSyncClient( taskRunner: threadTaskRunner, isVariableRefreshRateEnabled: false, maxRefreshRate: 60.0 ) { _, _ in } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) vsyncClient.allowPauseAfterVsync = false vsyncClient.await() vsyncClient.onDisplayLink(link) - XCTAssertFalse(link.isPaused) + #expect(link.isPaused == false) vsyncClient.allowPauseAfterVsync = true vsyncClient.await() vsyncClient.onDisplayLink(link) - XCTAssertTrue(link.isPaused) + #expect(link.isPaused) } - func testSetCorrectVariableRefreshRates() { + @Test func setCorrectVariableRefreshRates() throws { let maxFrameRate: Double = 120.0 let vsyncClient = VSyncClient( taskRunner: threadTaskRunner, isVariableRefreshRateEnabled: true, maxRefreshRate: maxFrameRate ) { _, _ in } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) if #available(iOS 15.0, *) { - XCTAssertEqual(Double(link.preferredFrameRateRange.maximum), maxFrameRate, accuracy: 0.1) - XCTAssertEqual( - Double(link.preferredFrameRateRange.preferred ?? 0), maxFrameRate, accuracy: 0.1) - XCTAssertEqual(Double(link.preferredFrameRateRange.minimum), maxFrameRate / 2, accuracy: 0.1) + #expect(abs(Double(link.preferredFrameRateRange.maximum) - maxFrameRate) <= 0.1) + #expect(abs(Double(link.preferredFrameRateRange.preferred ?? 0) - maxFrameRate) <= 0.1) + #expect(abs(Double(link.preferredFrameRateRange.minimum) - maxFrameRate / 2) <= 0.1) } else { - XCTAssertEqual(Double(link.preferredFramesPerSecond), maxFrameRate, accuracy: 0.1) + #expect(abs(Double(link.preferredFramesPerSecond) - maxFrameRate) <= 0.1) } } - func testDoNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotOn() { + @Test func doNotSetVariableRefreshRatesIfCADisableMinimumFrameDurationOnPhoneIsNotOn() throws { let maxFrameRate: Double = 120.0 let vsyncClient = VSyncClient( taskRunner: threadTaskRunner, isVariableRefreshRateEnabled: false, maxRefreshRate: maxFrameRate ) { _, _ in } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) if #available(iOS 15.0, *) { - XCTAssertEqual(Double(link.preferredFrameRateRange.maximum), 0, accuracy: 0.1) - XCTAssertEqual(Double(link.preferredFrameRateRange.preferred ?? 0), 0, accuracy: 0.1) - XCTAssertEqual(Double(link.preferredFrameRateRange.minimum), 0, accuracy: 0.1) + #expect(abs(Double(link.preferredFrameRateRange.maximum)) <= 0.1) + #expect(abs(Double(link.preferredFrameRateRange.preferred ?? 0)) <= 0.1) + #expect(abs(Double(link.preferredFrameRateRange.minimum)) <= 0.1) } else { - XCTAssertEqual(Double(link.preferredFramesPerSecond), 0, accuracy: 0.1) + #expect(abs(Double(link.preferredFramesPerSecond)) <= 0.1) } } - func testAwaitAndPauseWillWorkCorrectly() { + @Test func awaitAndPauseWillWorkCorrectly() throws { let vsyncClient = VSyncClient( taskRunner: threadTaskRunner, isVariableRefreshRateEnabled: false, maxRefreshRate: 60.0 ) { _, _ in } - let link = vsyncClient.displayLink! + let link = try #require(vsyncClient.displayLink) - XCTAssertTrue(link.isPaused) + #expect(link.isPaused) vsyncClient.await() - XCTAssertFalse(link.isPaused) + #expect(link.isPaused == false) vsyncClient.pause() - XCTAssertTrue(link.isPaused) + #expect(link.isPaused) } - func testReleasesLinkOnInvalidation() { - let threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "FlutterVSyncClientTest") + @Test func releasesLinkOnInvalidation() { weak var weakClient: VSyncClient? autoreleasepool { - let vsyncExpectation = expectation(description: "vsync") + let vsyncSignal = DispatchSemaphore(value: 0) let client = VSyncClient( taskRunner: threadTaskRunner, isVariableRefreshRateEnabled: false, maxRefreshRate: 60.0 ) { _, _ in - vsyncExpectation.fulfill() + vsyncSignal.signal() } weakClient = client @@ -184,22 +173,21 @@ class VSyncClientTests: XCTestCase { client.await() } - waitForExpectations(timeout: 1.0, handler: nil) + #expect(vsyncSignal.wait(timeout: .now() + 1.0) == .success) client.invalidate() } - let backgroundThreadFlushed = expectation(description: "Background thread flushed") + let backgroundThreadFlushed = DispatchSemaphore(value: 0) threadTaskRunner.postTask { - backgroundThreadFlushed.fulfill() + backgroundThreadFlushed.signal() } - waitForExpectations(timeout: 1.0, handler: nil) - XCTAssertNil(weakClient) + #expect(backgroundThreadFlushed.wait(timeout: .now() + 1.0) == .success) + #expect(weakClient == nil) } - func testDeallocatesWithoutExplicitInvalidation() { - let threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "VSyncClientTest") + @Test func deallocatesWithoutExplicitInvalidation() { weak var weakClient: VSyncClient? autoreleasepool { @@ -211,15 +199,14 @@ class VSyncClientTests: XCTestCase { weakClient = client } - XCTAssertNil(weakClient) + #expect(weakClient == nil) } /// Verifies there is no retain cycle through the display-link → relay → client chain after /// the display server has taken ownership of the link. On iOS 27+, QuartzCore holds a /// `_CADisplayLinkAssertion` on registered links; a never-unpaused link may therefore /// outlive `VSyncClient` itself, which is expected. - func testDeallocatesAfterRegistrationCompletes() { - let threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "VSyncClientTest") + @Test func deallocatesAfterRegistrationCompletes() { weak var weakClient: VSyncClient? autoreleasepool { @@ -233,11 +220,11 @@ class VSyncClientTests: XCTestCase { // Registration is dispatched to the task runner in init. Post a barrier task after it // so we know registration has completed before deinit fires. - let registerExpectation = expectation(description: "Wait for display link registration") - threadTaskRunner.postTask { registerExpectation.fulfill() } - waitForExpectations(timeout: 1.0, handler: nil) + let registered = DispatchSemaphore(value: 0) + threadTaskRunner.postTask { registered.signal() } + #expect(registered.wait(timeout: .now() + 1.0) == .success) } - XCTAssertNil(weakClient) + #expect(weakClient == nil) } } From 59c14b661c6cf20f60200b5ff5c9cd474d654210 Mon Sep 17 00:00:00 2001 From: Bruno Corona Date: Tue, 28 Jul 2026 19:09:55 -0600 Subject: [PATCH 083/147] Fix TreeSliver first node clipping during expand/collapse animation (#188626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description While a `TreeSliver` node expands/collapses, its children should be clipped beneath the node (at the node's trailing edge). This worked for every node except the first: node index 0 clipped at its *leading* edge, so its children painted over the node during the animation. The cause was a special case in `RenderTreeSliver.paint` that conflated "no parent" with "parent is index 0": ```dart (parentIndex == 0 ? 0.0 : itemExtentBuilder(parentIndex, layoutDimensions)!) ``` This branch only runs for the clipped segments after the first (the first segment is painted unclipped beforehand), so `parentIndex` is always a real animating parent whose extent must be added — including index 0. The fix always adds the parent's extent. ## Demo Video showing the bug and the fix: https://github.com/user-attachments/assets/db19c35c-c249-4a92-afde-5fb1e6f9f2fb ## Related Issues Fixes https://github.com/flutter/flutter/issues/188305 ## Tests I added the following tests: Regression tests in `sliver_tree_test.dart` that pump the toggle animation to its midpoint and assert the clip-rect top edge: - First node (index 0) clips at its trailing edge (`rowExtent`), not `0.0`. - Non-first node (index 1) clips at its trailing edge (`rowExtent * 2`), confirming no regression for other nodes. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../lib/src/rendering/sliver_tree.dart | 8 +- .../test/widgets/sliver_tree_test.dart | 106 ++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/rendering/sliver_tree.dart b/packages/flutter/lib/src/rendering/sliver_tree.dart index 5edb2a4b07aef..d51c68dd8851d 100644 --- a/packages/flutter/lib/src/rendering/sliver_tree.dart +++ b/packages/flutter/lib/src/rendering/sliver_tree.dart @@ -371,10 +371,14 @@ class RenderTreeSliver extends RenderSliverVariedExtentList { // leadingIndex), and the trailing edge of the trailing index. We cannot // rely on the leading edge of the leading index, because it is currently // moving. + // + // parentIndex is always a real animating node (the unclipped first + // segment is already painted), so its extent is added even when it is + // index 0. Otherwise the clip starts at the parent's leading edge and its + // children paint over it. final int parentIndex = math.max(segment.leadingIndex - 1, 0); final double leadingOffset = - indexToLayoutOffset(0.0, parentIndex) + - (parentIndex == 0 ? 0.0 : itemExtentBuilder(parentIndex, layoutDimensions)!); + indexToLayoutOffset(0.0, parentIndex) + itemExtentBuilder(parentIndex, layoutDimensions)!; final double trailingOffset = indexToLayoutOffset(0.0, segment.trailingIndex) + itemExtentBuilder(segment.trailingIndex, layoutDimensions)!; diff --git a/packages/flutter/test/widgets/sliver_tree_test.dart b/packages/flutter/test/widgets/sliver_tree_test.dart index 7b97a4d9eee6f..a48a373e5707f 100644 --- a/packages/flutter/test/widgets/sliver_tree_test.dart +++ b/packages/flutter/test/widgets/sliver_tree_test.dart @@ -1006,4 +1006,110 @@ void main() { await expectLater(find.byKey(key), matchesGoldenFile('sliver_tree.scrolling.1.png')); expect(tester.getTopLeft(find.byType(ColoredBox)), const Offset(0, -5)); }); + + group('Animating node clips children below its trailing edge', () { + // Regression test for https://github.com/flutter/flutter/issues/188305. + // While a node expands, its children must be clipped beneath the node (at the + // node's trailing edge) - including the first node, which used to clip at its + // leading edge and let its children paint over it. + const rowExtent = 40.0; + const animationDuration = Duration(seconds: 1); + + // The top edge of every clip rect the sliver paints. + List recordedClipTops(RenderObject renderObject) { + final tops = []; + final canvas = TestRecordingCanvas(); + final context = TestRecordingPaintingContext(canvas); + renderObject.paint(context, Offset.zero); + for (final RecordedInvocation recorded in canvas.invocations) { + if (recorded.invocation.memberName == #clipRect) { + tops.add((recorded.invocation.positionalArguments[0] as Rect).top); + } + } + return tops; + } + + Widget buildTree(TreeSliverController controller) { + final tree = >[ + TreeSliverNode( + 'First', + children: >[ + TreeSliverNode('First:0'), + TreeSliverNode('First:1'), + ], + ), + TreeSliverNode( + 'Second', + children: >[ + TreeSliverNode('Second:0'), + TreeSliverNode('Second:1'), + ], + ), + ]; + return Directionality( + textDirection: TextDirection.ltr, + child: CustomScrollView( + slivers: [ + TreeSliver( + tree: tree, + controller: controller, + treeRowExtentBuilder: (_, _) => rowExtent, + toggleAnimationStyle: const AnimationStyle( + curve: Curves.linear, + duration: animationDuration, + ), + treeNodeBuilder: + ( + BuildContext context, + TreeSliverNode node, + AnimationStyle animationStyle, + ) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => controller.toggleNode(node), + child: TreeSliver.defaultTreeNodeBuilder(context, node, animationStyle), + ); + }, + ), + ], + ), + ); + } + + testWidgets('first node (index 0) clips at its trailing edge', (WidgetTester tester) async { + final controller = TreeSliverController(); + await tester.pumpWidget(buildTree(controller)); + + await tester.tap(find.text('First')); + await tester.pump(); + await tester.pump(animationDuration ~/ 2); // Mid-animation. + + final RenderTreeSliver renderTree = tester.renderObject( + find.byType(TreeSliver), + ); + final List clipTops = recordedClipTops(renderTree); + // One row down (the node's trailing edge), not at the top (0.0). + expect(clipTops, isNotEmpty); + expect(clipTops, contains(rowExtent)); + expect(clipTops, isNot(contains(0.0))); + }); + + testWidgets('non-first node (index 1) clips at its trailing edge', (WidgetTester tester) async { + final controller = TreeSliverController(); + await tester.pumpWidget(buildTree(controller)); + + await tester.tap(find.text('Second')); + await tester.pump(); + await tester.pump(animationDuration ~/ 2); // Mid-animation. + + final RenderTreeSliver renderTree = tester.renderObject( + find.byType(TreeSliver), + ); + final List clipTops = recordedClipTops(renderTree); + // The second node is at index 1, so its trailing edge is two rows down. + expect(clipTops, isNotEmpty); + expect(clipTops, contains(rowExtent * 2)); + expect(clipTops, isNot(contains(0.0))); + }); + }); } From 2a230d1e8037a412a52374bda191dccfb2a1bd61 Mon Sep 17 00:00:00 2001 From: awnoa Date: Wed, 29 Jul 2026 03:20:08 +0200 Subject: [PATCH 084/147] Clarify CustomScrollView use cases (#189692) This updates the `CustomScrollView` API documentation to describe its common use case more directly: combining different kinds of scrollable content. The introduction now also calls out `SliverToBoxAdapter`, so readers can see how regular box widgets fit alongside lists, grids, and expanding headers. Fixes #142673. Tests: not run (documentation-only change; comment-only changes are automatically test-exempt). ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/widgets/scroll_view.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index fbe1415080821..8d8f6548c1896 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -587,13 +587,15 @@ abstract class ScrollView extends StatelessWidget { } } -/// A [ScrollView] that creates custom scroll effects using [slivers]. -/// -/// A [CustomScrollView] lets you supply [slivers] directly to create various -/// scrolling effects, such as lists, grids, and expanding headers. For example, -/// to create a scroll view that contains an expanding app bar followed by a -/// list and a grid, use a list of three slivers: [SliverAppBar], [SliverList], -/// and [SliverGrid]. +/// A [ScrollView] that combines multiple [slivers] in one scrollable view. +/// +/// A [CustomScrollView] lets you combine lists, grids, and other widgets in a +/// single scrollable view by supplying [slivers] directly. Slivers can +/// represent lists, grids, and expanding headers. Widgets that use the box +/// layout model can be included using a [SliverToBoxAdapter]. For example, to +/// create a scroll view that contains an expanding app bar followed by a list +/// and a grid, use a list of three slivers: [SliverAppBar], [SliverList], and +/// [SliverGrid]. /// /// [Widget]s in these [slivers] must produce [RenderSliver] objects. /// @@ -716,7 +718,8 @@ abstract class ScrollView extends StatelessWidget { /// * [IndexedSemantics], which allows annotating child lists with an index /// for scroll announcements. class CustomScrollView extends ScrollView { - /// Creates a [ScrollView] that creates custom scroll effects using slivers. + /// Creates a [ScrollView] that combines multiple slivers in one scrollable + /// view. /// /// See the [ScrollView] constructor for more details on these arguments. const CustomScrollView({ From a92b95206b981c6f1570ab427f35392938fc01fa Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 29 Jul 2026 01:58:00 +0000 Subject: [PATCH 085/147] Manual roll of Dart from 28e63ac22d8d to 49c637261348 (#190158) This contains a change to the Dart analyzer that affects how exclude paths are used in analysis_options.yaml files. (see https://dart.googlesource.com/sdk.git/+/7c44c487f41e3a19bafa1fb7c309d6035d5e036b) fixes https://github.com/flutter/flutter/issues/190169 --- DEPS | 2 +- analysis_options.yaml | 264 +--------------------- analysis_options_common.yaml | 265 +++++++++++++++++++++++ engine/src/flutter/analysis_options.yaml | 2 +- 4 files changed, 268 insertions(+), 265 deletions(-) create mode 100644 analysis_options_common.yaml diff --git a/DEPS b/DEPS index 0a004917a8e7c..fa72ca493aa86 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '28e63ac22d8dbb455fc7d615c2f7c2e1513f930a', + 'dart_revision': '49c6372613486187900a553177de4af3faafb50c', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py diff --git a/analysis_options.yaml b/analysis_options.yaml index 4c40f88316561..74a5e1e3b0eeb 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,6 @@ -# Specify analysis options. -# -# For a list of lints, see: https://dart.dev/tools/linter-rules -# For guidelines on configuring static analysis, see: -# https://dart.dev/tools/analysis -# -# There are other similar analysis options files in the flutter repos, -# which should be kept in sync with this file: -# -# - analysis_options.yaml (this file) -# - https://github.com/flutter/packages/blob/main/analysis_options.yaml -# -# This file contains the analysis options used for code in the flutter/flutter -# repository. +include: analysis_options_common.yaml analyzer: - language: - strict-casts: true - strict-inference: true - errors: - # allow deprecated members (we do this because otherwise we have to annotate - # every member in every test, assert, etc, when we or the Dart SDK deprecates - # something (https://github.com/flutter/flutter/issues/143312) - deprecated_member_use: ignore - deprecated_member_use_from_same_package: ignore exclude: - "bin/cache/**" # Ignore protoc generated files @@ -35,243 +13,3 @@ analyzer: - windows/** - macos/** - linux/** - -formatter: - page_width: 100 - -linter: - rules: - # This list is derived from the list of all available lints located at - # https://github.com/dart-lang/sdk/blob/main/pkg/linter/example/all.yaml - - always_declare_return_types - - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - # - always_specify_types # conflicts with omit_obvious_local_variable_types - # - always_use_package_imports # we do this commonly - - annotate_overrides - - annotate_redeclares - # - avoid_annotating_with_dynamic # conflicts with type_annotate_public_apis - - avoid_bool_literals_in_conditional_expressions - # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 - # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/4998 - # - avoid_classes_with_only_static_members # we do this commonly for `abstract final class`es - - avoid_double_and_int_checks - - avoid_dynamic_calls - - avoid_empty_else - - avoid_equals_and_hash_code_on_mutable_classes - - avoid_escaping_inner_quotes - - avoid_field_initializers_in_const_classes - # TODO(kallentu): Remove this lint once the Dart SDK in Flutter is on version 3.13. - - avoid_final_parameters - - avoid_function_literals_in_foreach_calls - # - avoid_futureor_void # not yet tested - # - avoid_implementing_value_types # see https://github.com/dart-lang/linter/issues/4558 - - avoid_init_to_null - - avoid_js_rounded_ints - # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to - # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it - - avoid_print - # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - - avoid_redundant_argument_values - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - - avoid_returning_null_for_void - # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives - - avoid_setters_without_getters - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_slow_async_io - - avoid_type_to_string - - avoid_types_as_parameter_names - # - avoid_types_on_closure_parameters # not yet tested - - avoid_unnecessary_containers - - avoid_unused_constructor_parameters - - avoid_void_async - # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere - - await_only_futures - - camel_case_extensions - - camel_case_types - - cancel_subscriptions - # - cascade_invocations # doesn't match the typical style of this repo - - cast_nullable_to_non_nullable - # - close_sinks # not reliable enough - - collection_methods_unrelated_type - - combinators_ordering - # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 - - conditional_uri_does_not_exist - # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - dangling_library_doc_comments - - depend_on_referenced_packages - - deprecated_consistency - # - deprecated_member_use_from_same_package # we allow self-references to deprecated members - # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) - - directives_ordering - # - discarded_futures # too many false positives, similar to unawaited_futures - # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic - # - document_ignores # not yet tested - - empty_catches - - empty_constructor_bodies - - empty_statements - - eol_at_end_of_file - - exhaustive_cases - - file_names - - flutter_style_todos - - hash_and_equals - - implementation_imports - - implicit_call_tearoffs - - implicit_reopen - - invalid_case_patterns - - invalid_runtime_check_with_js_interop_types - # - join_return_with_assignment # not required by flutter style - - leading_newlines_in_multiline_strings - - library_annotations - - library_names - - library_prefixes - - library_private_types_in_public_api - # - lines_longer_than_80_chars # not required by flutter style - - literal_only_boolean_expressions - # - matching_super_parameters # blocked on https://github.com/dart-lang/language/issues/2509 - - missing_code_block_language_in_doc_comment - - missing_whitespace_between_adjacent_strings - - no_adjacent_strings_in_list - - no_default_cases - - no_duplicate_case_values - - no_leading_underscores_for_library_prefixes - - no_leading_underscores_for_local_identifiers - - no_literal_bool_comparisons - - no_logic_in_create_state - - no_raw_types - # - no_runtimeType_toString # ok in tests; we enable this only in packages/ - - no_self_assignments - - no_wildcard_variable_uses - - non_constant_identifier_names - - noop_primitive_operations - - null_check_on_nullable_type_parameter - - null_closures - # - omit_local_variable_types # superset of omit_obvious_local_variable_types - - omit_obvious_local_variable_types # not yet tested - # - omit_obvious_property_types # conflicts with type_annotate_public_apis - # - one_member_abstracts # too many false positives - - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al - - overridden_fields - - package_names - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_asserts_in_initializer_lists - # - prefer_asserts_with_message # not required by flutter style - - prefer_collection_literals - - prefer_conditional_assignment - - prefer_const_constructors - - prefer_const_constructors_in_immutables - - prefer_const_declarations - - prefer_const_literals_to_create_immutables - # - prefer_constructors_over_static_methods # far too many false positives - - prefer_contains - # - prefer_double_quotes # opposite of prefer_single_quotes - # - prefer_expression_function_bodies # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#consider-using--for-short-functions-and-methods - - prefer_final_fields - - prefer_final_in_for_each - - prefer_final_locals - - prefer_for_elements_to_map_fromIterable - - prefer_foreach - - prefer_function_declarations_over_variables - - prefer_generic_function_type_aliases - - prefer_if_elements_to_conditional_expressions - - prefer_if_null_operators - - prefer_initializing_formals - - prefer_inlined_adds - # - prefer_int_literals # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#use-double-literals-for-double-constants - - prefer_interpolation_to_compose_strings - - prefer_is_empty - - prefer_is_not_empty - - prefer_is_not_operator - - prefer_iterable_whereType - - prefer_mixin - # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere - - prefer_null_aware_operators - - prefer_relative_imports - - prefer_single_quotes - - prefer_spread_collections - - prefer_typing_uninitialized_variables - - prefer_void_to_null - - provide_deprecation_message - # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - - recursive_getters - # - require_trailing_commas # would be nice, but requires a lot of manual work: 10,000+ code locations would need to be reformatted by hand after bulk fix is applied - - secure_pubspec_urls - - sized_box_for_whitespace - - sized_box_shrink_expand - - slash_for_doc_comments - - sort_child_properties_last - - sort_constructors_first - # - sort_pub_dependencies # prevents separating pinned transitive dependencies - - sort_unnamed_constructors_first - - specify_nonobvious_local_variable_types - - specify_nonobvious_property_types - - strict_top_level_inference - - test_types_in_equals - - throw_in_finally - - tighten_type_of_initializing_formals - - type_annotate_public_apis - - type_init_formals - - type_literal_in_constant_pattern - # - unawaited_futures # too many false positives, especially with the way AnimationController works - # - unintended_html_in_doc_comment # blocked on https://github.com/dart-lang/linter/issues/5065 - # - unnecessary_async # not yet tested - - unnecessary_await_in_return - - unnecessary_brace_in_string_interps - - unnecessary_breaks - - unnecessary_const - - unnecessary_constructor_name - # - unnecessary_final # conflicts with prefer_final_locals - - unnecessary_getters_setters - # - unnecessary_ignore # Disabled by default to simplify migrations; should be periodically enabled locally to clean up offenders - # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - - unnecessary_late - - unnecessary_library_directive - # - unnecessary_library_name # blocked on https://github.com/dart-lang/dartdoc/issues/3882 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_aware_operator_on_extension_on_nullable - - unnecessary_null_checks - - unnecessary_null_in_if_null_operators - - unnecessary_nullable_for_final_variable_declarations - - unnecessary_overrides - - unnecessary_parenthesis - # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint - - unnecessary_statements - - unnecessary_string_escapes - - unnecessary_string_interpolations - - unnecessary_this - - unnecessary_to_list_in_spreads - - unnecessary_underscores - - unreachable_from_main - - unrelated_type_equality_checks - # - unsafe_variance # not yet tested - - use_build_context_synchronously - - use_colored_box - # - use_decorated_box # leads to bugs: DecoratedBox and Container are not equivalent (Container inserts extra padding) - - use_enums - - use_full_hex_values_for_flutter_colors - - use_function_type_syntax_for_parameters - - use_is_even_rather_than_modulo - - use_key_in_widget_constructors - - use_late_for_private_fields_and_variables - - use_named_constants - - use_raw_strings - - use_rethrow_when_possible - - use_setters_to_change_properties - # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - - use_string_in_part_of_directives - - use_super_parameters - - use_test_throws_matchers - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - use_truncating_division - - valid_regexps - # TODO(kallentu): Remove this lint once the Dart SDK in Flutter is on version 3.13. - - var_with_no_type_annotation - - void_checks diff --git a/analysis_options_common.yaml b/analysis_options_common.yaml new file mode 100644 index 0000000000000..79e8ce2d77b68 --- /dev/null +++ b/analysis_options_common.yaml @@ -0,0 +1,265 @@ +# Specify analysis options. +# +# For a list of lints, see: https://dart.dev/tools/linter-rules +# For guidelines on configuring static analysis, see: +# https://dart.dev/tools/analysis +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - https://github.com/flutter/packages/blob/main/analysis_options.yaml +# +# This file contains the analysis options used for code in the flutter/flutter +# repository. + +analyzer: + language: + strict-casts: true + strict-inference: true + errors: + # allow deprecated members (we do this because otherwise we have to annotate + # every member in every test, assert, etc, when we or the Dart SDK deprecates + # something (https://github.com/flutter/flutter/issues/143312) + deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore + +formatter: + page_width: 100 + +linter: + rules: + # This list is derived from the list of all available lints located at + # https://github.com/dart-lang/sdk/blob/main/pkg/linter/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + # - always_specify_types # conflicts with omit_obvious_local_variable_types + # - always_use_package_imports # we do this commonly + - annotate_overrides + - annotate_redeclares + # - avoid_annotating_with_dynamic # conflicts with type_annotate_public_apis + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 + # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/4998 + # - avoid_classes_with_only_static_members # we do this commonly for `abstract final class`es + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + # TODO(kallentu): Remove this lint once the Dart SDK in Flutter is on version 3.13. + - avoid_final_parameters + - avoid_function_literals_in_foreach_calls + # - avoid_futureor_void # not yet tested + # - avoid_implementing_value_types # see https://github.com/dart-lang/linter/issues/4558 + - avoid_init_to_null + - avoid_js_rounded_ints + # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to + # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it + - avoid_print + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # not yet tested + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # doesn't match the typical style of this repo + - cast_nullable_to_non_nullable + # - close_sinks # not reliable enough + - collection_methods_unrelated_type + - combinators_ordering + # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 + - conditional_uri_does_not_exist + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - dangling_library_doc_comments + - depend_on_referenced_packages + - deprecated_consistency + # - deprecated_member_use_from_same_package # we allow self-references to deprecated members + # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) + - directives_ordering + # - discarded_futures # too many false positives, similar to unawaited_futures + # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic + # - document_ignores # not yet tested + - empty_catches + - empty_constructor_bodies + - empty_statements + - eol_at_end_of_file + - exhaustive_cases + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - implicit_call_tearoffs + - implicit_reopen + - invalid_case_patterns + - invalid_runtime_check_with_js_interop_types + # - join_return_with_assignment # not required by flutter style + - leading_newlines_in_multiline_strings + - library_annotations + - library_names + - library_prefixes + - library_private_types_in_public_api + # - lines_longer_than_80_chars # not required by flutter style + - literal_only_boolean_expressions + # - matching_super_parameters # blocked on https://github.com/dart-lang/language/issues/2509 + - missing_code_block_language_in_doc_comment + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_default_cases + - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - no_literal_bool_comparisons + - no_logic_in_create_state + - no_raw_types + # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - no_self_assignments + - no_wildcard_variable_uses + - non_constant_identifier_names + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + # - omit_local_variable_types # superset of omit_obvious_local_variable_types + - omit_obvious_local_variable_types # not yet tested + # - omit_obvious_property_types # conflicts with type_annotate_public_apis + # - one_member_abstracts # too many false positives + - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al + - overridden_fields + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not required by flutter style + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # far too many false positives + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + # - prefer_expression_function_bodies # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#use-double-literals-for-double-constants + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere + - prefer_null_aware_operators + - prefer_relative_imports + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + # - require_trailing_commas # would be nice, but requires a lot of manual work: 10,000+ code locations would need to be reformatted by hand after bulk fix is applied + - secure_pubspec_urls + - sized_box_for_whitespace + - sized_box_shrink_expand + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + # - sort_pub_dependencies # prevents separating pinned transitive dependencies + - sort_unnamed_constructors_first + - specify_nonobvious_local_variable_types + - specify_nonobvious_property_types + - strict_top_level_inference + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + - type_annotate_public_apis + - type_init_formals + - type_literal_in_constant_pattern + # - unawaited_futures # too many false positives, especially with the way AnimationController works + # - unintended_html_in_doc_comment # blocked on https://github.com/dart-lang/linter/issues/5065 + # - unnecessary_async # not yet tested + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_breaks + - unnecessary_const + - unnecessary_constructor_name + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_ignore # Disabled by default to simplify migrations; should be periodically enabled locally to clean up offenders + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_late + - unnecessary_library_directive + # - unnecessary_library_name # blocked on https://github.com/dart-lang/dartdoc/issues/3882 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_aware_operator_on_extension_on_nullable + - unnecessary_null_checks + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unnecessary_to_list_in_spreads + - unnecessary_underscores + - unreachable_from_main + - unrelated_type_equality_checks + # - unsafe_variance # not yet tested + - use_build_context_synchronously + - use_colored_box + # - use_decorated_box # leads to bugs: DecoratedBox and Container are not equivalent (Container inserts extra padding) + - use_enums + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + - use_string_in_part_of_directives + - use_super_parameters + - use_test_throws_matchers + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - use_truncating_division + - valid_regexps + # TODO(kallentu): Remove this lint once the Dart SDK in Flutter is on version 3.13. + - var_with_no_type_annotation + - void_checks diff --git a/engine/src/flutter/analysis_options.yaml b/engine/src/flutter/analysis_options.yaml index a7188fc6b458a..6bc1082ce7873 100644 --- a/engine/src/flutter/analysis_options.yaml +++ b/engine/src/flutter/analysis_options.yaml @@ -7,7 +7,7 @@ # # The reasoning for deviating from the general style must be documented below. -include: ../../../analysis_options.yaml +include: ../../../analysis_options_common.yaml analyzer: exclude: From ec81c5f4c4effdb2539cd0063289753e1f892626 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 22:28:55 -0400 Subject: [PATCH 086/147] Roll Skia from 70733f74d415 to 30ad01017a46 (2 revisions) (#190173) https://skia.googlesource.com/skia.git/+log/70733f74d415..30ad01017a46 2026-07-29 jrosengren@microsoft.com Adding override for incremental decoding as both Rust PNG and Rust BMP support it while the base class SkCodec returns false. 2026-07-28 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 0d9c229af89c to 863cb98140c0 (4 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index fa72ca493aa86..c928b6af48ccd 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '70733f74d415bf389f6bda812e95e8aee6182664', + 'skia_revision': '30ad01017a46a31859b580bc907457b0e43907a8', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 9d9836621d8a7afaf83b047909b789e19fe38f1b Mon Sep 17 00:00:00 2001 From: David Miguel Lozano Date: Wed, 29 Jul 2026 04:56:20 +0200 Subject: [PATCH 087/147] Fix web context-menu client lifecycle in SelectableRegion (#189587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On web, `PlatformSelectableRegionContextMenu._activeClient` — the static `SelectionContainerDelegate` that the right-click `mousedown` listener reads — can end up pointing at a detached or disposed delegate, because of three lifecycle defects: 1. `detach` has an inverted condition — `if (_activeClient != client) { _activeClient = null; }`. Detaching the active client leaves it registered, while detaching an unrelated client wipes another region's registration. 2. `SelectableRegionState._handleFocusChanged` calls `attach` unconditionally at the end of the method, so a region that just lost focus is immediately re-registered. 3. `SelectableRegionState.dispose` never detaches, so the static can keep pointing at a disposed delegate. Together these produce a crash on web when the user right-clicks during a route transition: the listener calls `getTransformTo(null)` on the stale delegate and hits `"getTransformTo cannot be called before SelectionContainer is laid out"` (`selection_container.dart:303`). We hit this in a production web app. This PR flips the `detach` condition, moves the re-attach into the focused branch, and detaches in `dispose` before disposing the delegate. It also adds a `@visibleForTesting` `debugActiveClient` getter, mirroring the existing `debugOverrideRegisterViewFactory` pattern, since the delegate is otherwise unobservable from tests. Three new browser tests in `selectable_region_context_menu_test.dart` isolate the defects; each is red before the fix and green after it. A follow-up commit hardens the detach paths against `BrowserContextMenu.enabled` changing at runtime. Attach stays gated on the full dynamic check, but the focus-loss and dispose detach paths now gate on `kIsWeb` alone, so a delegate attached while the menu was enabled is still detached after the menu is disabled. This is safe because `detach` is identity-selective: calling it for a never-attached delegate is a no-op. Two more browser tests cover unfocus and disposal after `BrowserContextMenu.disableContextMenu()`, bringing it to five lifecycle tests, each red before its fix and green after. Fixes https://github.com/flutter/flutter/issues/189575 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- ...orm_selectable_region_context_menu_io.dart | 6 + ...rm_selectable_region_context_menu_web.dart | 8 +- .../lib/src/widgets/selectable_region.dart | 12 +- .../selectable_region_context_menu_test.dart | 262 ++++++++++++++++++ 4 files changed, 284 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_io.dart b/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_io.dart index b1035f3460d1e..e0bba3393a00d 100644 --- a/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_io.dart +++ b/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_io.dart @@ -40,6 +40,12 @@ class PlatformSelectableRegionContextMenu extends StatelessWidget { /// Detaches the `client` from the platform-appropriate selection context menus. static void detach(SelectionContainerDelegate client) => throw UnimplementedError(); + /// The client currently attached to the [PlatformSelectableRegionContextMenu]. + /// + /// This should only be used for testing. + @visibleForTesting + static SelectionContainerDelegate? get debugActiveClient => throw UnimplementedError(); + /// Override this to provide a custom implementation of `ui_web.platformViewRegistry.registerViewFactory`. /// /// This should only be used for testing. diff --git a/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_web.dart b/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_web.dart index 5965ac0c506a7..0883dcdc6a312 100644 --- a/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_web.dart +++ b/packages/flutter/lib/src/widgets/_platform_selectable_region_context_menu_web.dart @@ -56,13 +56,19 @@ class PlatformSelectableRegionContextMenu extends StatelessWidget { /// See `_platform_selectable_region_context_menu_io.dart`. static void detach(SelectionContainerDelegate client) { - if (_activeClient != client) { + if (_activeClient == client) { _activeClient = null; } } static SelectionContainerDelegate? _activeClient; + /// The client currently attached to the [PlatformSelectableRegionContextMenu]. + /// + /// This should only be used for testing. + @visibleForTesting + static SelectionContainerDelegate? get debugActiveClient => _activeClient; + // Keeps track if this widget has already registered its view factories or not. static String? _registeredViewType; diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index be97aa3518951..bb34e8a722e30 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -525,7 +525,11 @@ class SelectableRegionState extends State void _handleFocusChanged() { if (!_focusNode.hasFocus) { - if (_webContextMenuEnabled) { + if (kIsWeb) { + // Detach regardless of the current (dynamic) _webContextMenuEnabled + // value: the browser context menu may have been disabled after this + // delegate attached, and detach is a no-op for a client that was + // never the active one. PlatformSelectableRegionContextMenu.detach(_selectionDelegate); } if (SchedulerBinding.instance.lifecycleState == AppLifecycleState.resumed) { @@ -540,8 +544,7 @@ class SelectableRegionState extends State _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; _finalizeSelectableRegionStatus(); } - } - if (_webContextMenuEnabled) { + } else if (_webContextMenuEnabled) { PlatformSelectableRegionContextMenu.attach(_selectionDelegate); } } @@ -1935,6 +1938,9 @@ class SelectableRegionState extends State void dispose() { _selectable?.removeListener(_updateSelectionStatus); _selectable?.pushHandleLayers(null, null); + if (kIsWeb) { + PlatformSelectableRegionContextMenu.detach(_selectionDelegate); + } _selectionDelegate.dispose(); _selectionStatusNotifier.dispose(); // In case dispose was triggered before gesture end, remove the magnifier diff --git a/packages/flutter/test/widgets/selectable_region_context_menu_test.dart b/packages/flutter/test/widgets/selectable_region_context_menu_test.dart index 51248d7f7f2c6..bc4fccc21c1c9 100644 --- a/packages/flutter/test/widgets/selectable_region_context_menu_test.dart +++ b/packages/flutter/test/widgets/selectable_region_context_menu_test.dart @@ -5,6 +5,8 @@ @TestOn('browser') // This file contains web-only library. library; +import 'dart:js_interop'; + import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -142,6 +144,266 @@ void main() { expect((selectWordEvent.globalPosition.dy - 300).abs() < precisionErrorTolerance, isTrue); }, variant: _browserContextMenuEnabledVariants); + // Regression test for https://github.com/flutter/flutter/issues/189575. + testWidgets('right click does not dispatch event to previous stale client after losing focus', ( + WidgetTester tester, + ) async { + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + final spy = UniqueKey(); + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: SelectionSpy(key: spy), + ), + ), + ); + final element = fakePlatformViewRegistry.getViewById(currentViewId + 1) as web.HTMLElement; + expect(element, isNotNull); + focusNode.requestFocus(); + await tester.pump(); + focusNode.unfocus(); + await tester.pump(); + final RenderSelectionSpy renderSelectionSpy = tester.renderObject( + find.byKey(spy), + ); + renderSelectionSpy.events.clear(); + // Before the fix, losing focus re-attached the client instead of + // detaching it, so the right click below dispatched a + // SelectWordSelectionEvent to the stale, no-longer-focused client. + element.dispatchEvent( + web.MouseEvent('mousedown', web.MouseEventInit(button: 2, clientX: 200, clientY: 300)), + ); + expect(renderSelectionSpy.events, isEmpty); + }, variant: _browserContextMenuEnabledVariants); + + testWidgets('right click after the SelectableRegion is disposed does not crash', ( + WidgetTester tester, + ) async { + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ); + final element = fakePlatformViewRegistry.getViewById(currentViewId + 1) as web.HTMLElement; + expect(element, isNotNull); + focusNode.requestFocus(); + await tester.pump(); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNotNull); + + // Removing the SelectableRegion disposes its state without ever + // losing focus on the externally-owned focus node, so only the + // dispose-time detach can clear the static reference. + await tester.pumpWidget(const TestWidgetsApp(home: SizedBox.shrink())); + + // Before the fix, the static active-client pointer outlived the + // disposed delegate, so this right click reached into its defunct + // render context and crashed instead of being a no-op. + web.Event? capturedError; + final JSExportedDartFunction onWindowError = (web.Event event) { + capturedError = event; + }.toJS; + web.window.addEventListener('error', onWindowError); + addTearDown(() => web.window.removeEventListener('error', onWindowError)); + element.dispatchEvent( + web.MouseEvent('mousedown', web.MouseEventInit(button: 2, clientX: 200, clientY: 300)), + ); + expect(tester.takeException(), isNull); + expect(capturedError, isNull, reason: 'window reported an uncaught error: $capturedError'); + }, variant: _browserContextMenuEnabledVariants); + + testWidgets('detach only clears the active client when detaching the active client', ( + WidgetTester tester, + ) async { + final focusNodeA = FocusNode(); + final focusNodeB = FocusNode(); + addTearDown(focusNodeA.dispose); + addTearDown(focusNodeB.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: Column( + children: [ + SizedBox( + height: 100, + child: SelectableRegion( + focusNode: focusNodeA, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + SizedBox( + height: 100, + child: SelectableRegion( + focusNode: focusNodeB, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ], + ), + ), + ); + + focusNodeA.requestFocus(); + await tester.pump(); + final SelectionContainerDelegate delegateA = + PlatformSelectableRegionContextMenu.debugActiveClient!; + + focusNodeB.requestFocus(); + await tester.pump(); + final SelectionContainerDelegate delegateB = + PlatformSelectableRegionContextMenu.debugActiveClient!; + expect(delegateB, isNot(same(delegateA))); + + // Detaching a client that is not the active client must not clear + // the active client. + PlatformSelectableRegionContextMenu.detach(delegateA); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, same(delegateB)); + + // Detaching the active client must clear it. + PlatformSelectableRegionContextMenu.detach(delegateB); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNull); + }, variant: _browserContextMenuEnabledVariants); + + testWidgets('losing focus detaches the client and does not reattach it', ( + WidgetTester tester, + ) async { + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNotNull); + + focusNode.unfocus(); + await tester.pump(); + + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNull); + }, variant: _browserContextMenuEnabledVariants); + + testWidgets('disposing a SelectableRegion detaches its client from the context menu', ( + WidgetTester tester, + ) async { + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNotNull); + + // Removing the SelectableRegion disposes its state without ever + // losing focus on the externally-owned focus node, so only the + // dispose-time detach can clear the static reference. + await tester.pumpWidget(const TestWidgetsApp(home: SizedBox.shrink())); + + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNull); + }, variant: _browserContextMenuEnabledVariants); + + group('when the browser context menu is disabled after attaching', () { + setUp(() async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.contextMenu, + (MethodCall call) => Future.value(), + ); + }); + + tearDown(() async { + await BrowserContextMenu.enableContextMenu(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.contextMenu, + null, + ); + }); + + testWidgets('losing focus still detaches the client', (WidgetTester tester) async { + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNotNull); + + // Disabling the browser context menu after the delegate attached + // must not prevent the eventual detach: _webContextMenuEnabled is + // re-evaluated dynamically and would otherwise report false here. + await BrowserContextMenu.disableContextMenu(); + + focusNode.unfocus(); + await tester.pump(); + + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNull); + }, variant: _browserContextMenuEnabledVariants); + + testWidgets('disposing still detaches the client', (WidgetTester tester) async { + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: SelectableRegion( + focusNode: focusNode, + selectionControls: emptyTextSelectionControls, + child: const SelectionSpy(), + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNotNull); + + await BrowserContextMenu.disableContextMenu(); + + // Removing the SelectableRegion disposes its state without ever + // losing focus on the externally-owned focus node, so only the + // dispose-time detach can clear the static reference. + await tester.pumpWidget(const TestWidgetsApp(home: SizedBox.shrink())); + + expect(PlatformSelectableRegionContextMenu.debugActiveClient, isNull); + }, variant: _browserContextMenuEnabledVariants); + }); + // Regression test for https://github.com/flutter/flutter/issues/157579 testWidgets('prevents default action of mousedown events', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); From 3392c917d5615ebde5b16c11450001ed9e38df5b Mon Sep 17 00:00:00 2001 From: Bernardo Ferrari Date: Wed, 29 Jul 2026 00:16:24 -0300 Subject: [PATCH 088/147] Avoid unnecessary work in `BoxDecoration` and `RenderPhysicalModel` (#187891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/flutter/flutter/issues/187894 Improve `box_decoration.dart` In Flutter’s codebase that pattern already exists in similar hit-test code (RenderClipOval.hitTest https://github.com/flutter/flutter/blob/8c17c5e0677a5c923a9f606d8963e2e0350e6ec2/packages/flutter/lib/src/rendering/proxy_box.dart#L1932). Old code: ```dart final double distance = (position - center).distance; return distance <= math.min(size.width, size.height) / 2.0; ``` Offset.distance computes: `sqrt(dx * dx + dy * dy)` --- New code: ```dart final double radius = math.min(size.width, size.height) / 2.0; return (position - center).distanceSquared <= radius * radius; ``` Offset.distanceSquared computes only: `dx * dx + dy * dy` Then it compares that to radius * radius. Mathematically: `sqrt(dx² + dy²) <= r` is equivalent to: `dx² + dy² <= r²` Then, I also found an issue: `elevated` shadow path doesn't need to be initialized when there is no elevation to use it for. --- .../lib/src/painting/box_decoration.dart | 5 +++-- .../flutter/lib/src/rendering/proxy_box.dart | 2 +- .../test/painting/box_decoration_test.dart | 10 ++++++++++ .../test/rendering/proxy_box_test.dart | 19 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index ad12ffe4971e2..a9ca00e2c7408 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -384,8 +384,9 @@ class BoxDecoration extends Decoration { case BoxShape.circle: // Circles are inscribed into our smallest dimension. final Offset center = size.center(Offset.zero); - final double distance = (position - center).distance; - return distance <= math.min(size.width, size.height) / 2.0; + final double radius = math.min(size.width, size.height) / 2.0; + // Comparing squared distances avoids computing sqrt(dx * dx + dy * dy). + return (position - center).distanceSquared <= radius * radius; } } diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 18b5949ddb342..a98227645f87a 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2210,7 +2210,6 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase { _updateClip(); final RRect offsetRRect = _clip!.shift(offset); - final offsetRRectAsPath = Path()..addRRect(offsetRRect); var paintShadows = true; assert(() { if (debugDisableShadows) { @@ -2230,6 +2229,7 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase { final Canvas canvas = context.canvas; if (elevation != 0.0 && paintShadows) { + final offsetRRectAsPath = Path()..addRRect(offsetRRect); canvas.drawShadow(offsetRRectAsPath, shadowColor, elevation, color.alpha != 0xFF); } final usesSaveLayer = clipBehavior == Clip.antiAliasWithSaveLayer; diff --git a/packages/flutter/test/painting/box_decoration_test.dart b/packages/flutter/test/painting/box_decoration_test.dart index 5a8a9a5e2312d..c83ba00b8ad37 100644 --- a/packages/flutter/test/painting/box_decoration_test.dart +++ b/packages/flutter/test/painting/box_decoration_test.dart @@ -174,6 +174,16 @@ void main() { expect(clipPath, isLookLikeExpectedPath); }); + test('BoxDecoration.hitTest with shape BoxShape.circle', () { + const decoration = BoxDecoration(shape: BoxShape.circle); + const size = Size(100.0, 20.0); + + expect(decoration.hitTest(size, const Offset(50.0, 0.0)), isTrue); + expect(decoration.hitTest(size, const Offset(40.0, 10.0)), isTrue); + expect(decoration.hitTest(size, const Offset(40.0, 0.0)), isFalse); + expect(decoration.hitTest(size, const Offset(10.0, 10.0)), isFalse); + }); + test('BoxDecorations with different blendModes are not equal', () { // Regression test for https://github.com/flutter/flutter/issues/100754. const one = BoxDecoration(color: Color(0x00000000), backgroundBlendMode: BlendMode.color); diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index c0c8340aff1f2..75d728f2f8345 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -78,6 +78,25 @@ void main() { expect(root.needsCompositing, isFalse); }); + test('RenderPhysicalModel paints shadow only for non-zero elevation', () { + final root = RenderPhysicalModel( + color: const Color(0xffff00ff), + child: RenderSizedBox(const Size(10.0, 10.0)), + ); + layout(root, phase: EnginePhase.paint); + expect( + (PaintingContext context, Offset offset) => root.paint(context, offset), + paintsExactlyCountTimes(#drawShadow, 0), + ); + + root.elevation = 1.0; + pumpFrame(phase: EnginePhase.paint); + expect( + (PaintingContext context, Offset offset) => root.paint(context, offset), + paintsExactlyCountTimes(#drawShadow, 1), + ); + }); + test('RenderSemanticsGestureHandler adds/removes correct semantic actions', () { final renderObj = RenderSemanticsGestureHandler( onTap: () {}, From e47832bd1ccf074a65dadcc9e97df785d54fde06 Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Wed, 29 Jul 2026 05:24:08 +0200 Subject: [PATCH 089/147] Update widget location tracking to use the new generalized api (#189725) This refactors Flutters widget tracking setup to use the new generalized `CreationLocation` API introduced in https://github.com/dart-lang/sdk/commit/9e8fdef17c0a8a346e233188ba8170e9dc95bcc4 CC @dcharkes ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/widgets/framework.dart | 1 + .../lib/src/widgets/widget_inspector.dart | 99 ++++--------------- packages/flutter_tools/lib/src/compile.dart | 4 +- .../build_system/targets/common_test.dart | 12 +-- .../compile_incremental_test.dart | 2 +- .../test/general.shard/compile_test.dart | 4 +- 6 files changed, 33 insertions(+), 89 deletions(-) diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 4ef22ee6b6d8d..2bc2e3f3012b7 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -309,6 +309,7 @@ class GlobalObjectKey> extends GlobalKey { /// * [StatelessWidget], for widgets that always build the same way given a /// particular configuration and ambient state. @immutable +@pragma('track-creation-locations') abstract class Widget extends DiagnosticableTree { /// Initializes [key] for subclasses. const Widget({this.key}); diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index a27db16b35e3c..a81057ca949d3 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -27,7 +27,6 @@ import 'dart:ui' import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:meta/meta_meta.dart'; import 'basic.dart'; import 'binding.dart'; @@ -1640,7 +1639,7 @@ mixin WidgetInspectorService { void _notifyToolsOfSelection(Object? object, {bool restrictToProjectFiles = false}) { inspect(object); - final _Location? location = _getSelectedWidgetLocation( + final developer.CreationLocation? location = _getSelectedWidgetLocation( restrictToSummaryTree: restrictToProjectFiles, ); if (location != null) { @@ -1795,7 +1794,7 @@ mixin WidgetInspectorService { } bool _isValueCreatedByLocalProject(Object? value) { - final _Location? creationLocation = _getCreationLocation(value); + final developer.CreationLocation? creationLocation = _getCreationLocation(value); if (creationLocation == null) { return false; } @@ -2481,7 +2480,7 @@ mixin WidgetInspectorService { /// not in the summary tree (i.e. not created by the current project), this /// method will instead return the location of its nearest ancestor widget /// that is in the summary tree. - _Location? _getSelectedWidgetLocation({bool restrictToSummaryTree = false}) { + developer.CreationLocation? _getSelectedWidgetLocation({bool restrictToSummaryTree = false}) { final DiagnosticsNode? selectedNode = restrictToSummaryTree ? _getSelectedSummaryDiagnosticsNode(null) : _getSelectedWidgetDiagnosticsNode(null); @@ -2519,7 +2518,7 @@ mixin WidgetInspectorService { /// /// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree} bool isWidgetCreationTracked() { - _widgetCreationTracked ??= const _WidgetForTypeTests() is _HasCreationLocation; + _widgetCreationTracked ??= (developer.CreationLocation.of(const _WidgetForTypeTests()) != null); return _widgetCreationTracked!; } @@ -2642,7 +2641,7 @@ class _LocationCount { /// Whether the location is local to the current project. final bool local; - final _Location location; + final developer.CreationLocation location; int get count => _count; int _count = 0; @@ -2694,11 +2693,7 @@ class _ElementLocationStatsTracker { /// the creation location is local to the current project. void add(Element element) { final Object widget = element.widget; - if (widget is! _HasCreationLocation) { - return; - } - final _HasCreationLocation creationLocationSource = widget; - final _Location? location = creationLocationSource._location; + final developer.CreationLocation? location = developer.CreationLocation.of(widget); if (location == null) { return; } @@ -2772,7 +2767,7 @@ class _ElementLocationStatsTracker { // Add all newly used location ids to the JSON. final locationsJson = >{}; for (final _LocationCount entry in newLocations) { - final _Location location = entry.location; + final developer.CreationLocation location = entry.location; final List jsonForFile = locationsJson.putIfAbsent(location.file, () => []); jsonForFile ..add(entry.id) @@ -2786,7 +2781,7 @@ class _ElementLocationStatsTracker { if (newLocations.isNotEmpty) { final fileLocationsMap = >>{}; for (final _LocationCount entry in newLocations) { - final _Location location = entry.location; + final developer.CreationLocation location = entry.location; final Map> locations = fileLocationsMap.putIfAbsent( location.file, () => >{ @@ -4171,45 +4166,6 @@ class _ExitWidgetSelectionTooltipPainter extends CustomPainter { } } -/// Interface for classes that track the source code location the their -/// constructor was called from. -/// -/// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree} -// ignore: unused_element -abstract class _HasCreationLocation { - _Location? get _location; -} - -/// A tuple with file, line, and column number, for displaying human-readable -/// file locations. -class _Location { - const _Location({ - required this.file, - required this.line, - required this.column, - this.name, // ignore: unused_element_parameter - }); - - /// File path of the location. - final String file; - - /// 1-based line number. - final int line; - - /// 1-based column number. - final int column; - - /// Optional name of the parameter or function at this location. - final String? name; - - Map toJsonMap() { - return {'file': file, 'line': line, 'column': column, 'name': ?name}; - } - - @override - String toString() => [?name, file, '$line', '$column'].join(':'); -} - bool _isDebugCreator(DiagnosticsNode node) => node is DiagnosticsDebugCreator; /// Transformer to parse and gather information about [DiagnosticsDebugCreator]. @@ -4369,7 +4325,7 @@ class DevToolsDeepLinkProperty extends DiagnosticsProperty { bool debugIsLocalCreationLocation(Object object) { var isLocal = false; assert(() { - final _Location? location = _getCreationLocation(object); + final developer.CreationLocation? location = _getCreationLocation(object); if (location != null) { isLocal = WidgetInspectorService.instance._isLocalCreationLocation(location.file); } @@ -4383,7 +4339,7 @@ bool debugIsLocalCreationLocation(Object object) { /// This is a faster variant of `debugIsLocalCreationLocation` that is available /// in debug and profile builds but only works for [Widget]. bool debugIsWidgetLocalCreation(Widget widget) { - final _Location? location = _getObjectCreationLocation(widget); + final developer.CreationLocation? location = developer.CreationLocation.of(widget); return location != null && WidgetInspectorService.instance._isLocalCreationLocation(location.file); } @@ -4396,31 +4352,27 @@ bool debugIsWidgetLocalCreation(Widget widget) { /// /// Currently creation locations are only available for [Widget] and [Element]. String? _describeCreationLocation(Object object) { - final _Location? location = _getCreationLocation(object); + final developer.CreationLocation? location = _getCreationLocation(object); return location?.toString(); } -_Location? _getObjectCreationLocation(Object object) { - return object is _HasCreationLocation ? object._location : null; -} - /// Returns the creation location of an object if one is available. /// /// {@macro flutter.widgets.WidgetInspectorService.getChildrenSummaryTree} /// /// Currently creation locations are only available for [Widget] and [Element]. -_Location? _getCreationLocation(Object? object) { +developer.CreationLocation? _getCreationLocation(Object? object) { final Object? candidate = object is Element && !object.debugIsDefunct ? object.widget : object; - return candidate == null ? null : _getObjectCreationLocation(candidate); + return candidate == null ? null : developer.CreationLocation.of(candidate); } -// _Location objects are always const so we don't need to worry about the GC +// CreationLocation objects are always const so we don't need to worry about the GC // issues that are a concern for other object ids tracked by // [WidgetInspectorService]. -final Map<_Location, int> _locationToId = <_Location, int>{}; -final List<_Location> _locations = <_Location>[]; +final Map _locationToId = {}; +final List _locations = []; -int _toLocationId(_Location location) { +int _toLocationId(developer.CreationLocation location) { int? id = _locationToId[location]; if (id != null) { return id; @@ -4438,8 +4390,8 @@ Map _locationIdMapToJson() { const namesKey = 'names'; final fileLocationsMap = >>{}; - for (final MapEntry<_Location, int> entry in _locationToId.entries) { - final _Location location = entry.key; + for (final MapEntry entry in _locationToId.entries) { + final developer.CreationLocation location = entry.key; final Map> locations = fileLocationsMap.putIfAbsent( location.file, () => >{ @@ -4527,7 +4479,7 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate if (_interactive) { result['valueId'] = service.toId(value, groupName!); } - final _Location? creationLocation = _getCreationLocation(value); + final developer.CreationLocation? creationLocation = _getCreationLocation(value); if (creationLocation != null) { if (fullDetails) { result['locationId'] = _toLocationId(creationLocation); @@ -4601,11 +4553,6 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate } } -@Target({TargetKind.method}) -class _WidgetFactory { - const _WidgetFactory(); -} - /// Annotation which marks a function as a widget factory for the purpose of /// widget creation tracking. /// @@ -4660,11 +4607,7 @@ class _WidgetFactory { /// See also: /// /// * the documentation for [Track widget creation](https://flutter.dev/to/track-widget-creation). -// The below ignore is needed because the static type of the annotation is used -// by the CFE kernel transformer that implements the instrumentation to -// recognize the annotation. -// ignore: library_private_types_in_public_api -const _WidgetFactory widgetFactory = _WidgetFactory(); +const widgetFactory = pragma('track-creation-locations'); /// Does not hold keys from garbage collection. @visibleForTesting diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index d11863c167c5c..51d2c2797c7d4 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -359,7 +359,7 @@ class KernelCompiler { '--no-print-incremental-dependencies', for (final Object dartDefine in dartDefines) '-D$dartDefine', ...buildModeOptions(buildMode, dartDefines), - if (trackWidgetCreation) '--track-widget-creation', + if (trackWidgetCreation) '--track-creation-locations', if (!linkPlatformKernelIn) '--no-link-platform', if (aot) ...[ '--aot', @@ -974,7 +974,7 @@ class DefaultResidentCompiler implements ResidentCompiler { ], if (packagesPath != null) ...['--packages', packagesPath!], ...buildModeOptions(buildMode, dartDefines), - if (trackWidgetCreation) '--track-widget-creation', + if (trackWidgetCreation) '--track-creation-locations', if (includeUnsupportedPlatformLibraryStubs) '--include-unsupported-platform-library-stubs', for (final String root in fileSystemRoots) ...['--filesystem-root', root], if (fileSystemScheme != null) ...['--filesystem-scheme', fileSystemScheme!], 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 f4fc8918de625..64f77221e483f 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 @@ -101,7 +101,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.profile, []), - '--track-widget-creation', + '--track-creation-locations', '--aot', '--tfa', '--target-os', @@ -143,7 +143,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.profile, []), - '--track-widget-creation', + '--track-creation-locations', '--aot', '--tfa', '--target-os', @@ -188,7 +188,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.profile, []), - '--track-widget-creation', + '--track-creation-locations', '--aot', '--tfa', '--target-os', @@ -233,7 +233,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.profile, []), - '--track-widget-creation', + '--track-creation-locations', '--aot', '--tfa', '--target-os', @@ -279,7 +279,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.profile, []), - '--track-widget-creation', + '--track-creation-locations', '--aot', '--tfa', '--target-os', @@ -647,7 +647,7 @@ void main() { '--target=flutter', '--no-print-incremental-dependencies', ...buildModeOptions(BuildMode.debug, []), - '--track-widget-creation', + '--track-creation-locations', '--no-link-platform', '--packages', '/.dart_tool/package_config.json', diff --git a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart index c773fbfd44acc..d1881a0e19018 100644 --- a/packages/flutter_tools/test/general.shard/compile_incremental_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_incremental_test.dart @@ -47,7 +47,7 @@ void main() { '-Ddart.vm.profile=false', '-Ddart.vm.product=false', '--enable-asserts', - '--track-widget-creation', + '--track-creation-locations', ]; setUp(() { diff --git a/packages/flutter_tools/test/general.shard/compile_test.dart b/packages/flutter_tools/test/general.shard/compile_test.dart index 8d37a124a35ba..0be122136050a 100644 --- a/packages/flutter_tools/test/general.shard/compile_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_test.dart @@ -224,7 +224,7 @@ void main() { '-Ddart.vm.profile=false', '-Ddart.vm.product=false', '--enable-asserts', - '--track-widget-creation', + '--track-creation-locations', '--initialize-from-dill', 'build/0ca43a24517cbfd39e8c3fdfd86bd8d9.cache.dill.track.dill', '--verbosity=error', @@ -299,7 +299,7 @@ void main() { '-Ddart.vm.profile=false', '-Ddart.vm.product=false', '--enable-asserts', - '--track-widget-creation', + '--track-creation-locations', '--include-unsupported-platform-library-stubs', '--initialize-from-dill', 'build/d484347ee69722eb276c222b372bed02.cache.dill.track.dill', From cb0b0dd674c1f2d39700ca70dab0c902a282bd88 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 28 Jul 2026 23:41:23 -0400 Subject: [PATCH 090/147] Roll Fuchsia Linux SDK from OZkZC_2CZ_G5rbMIS... to J8rVTlBnjnUpuHYk3... (#190174) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC aaclarke@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c928b6af48ccd..0fccca2f3c599 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'OZkZC_2CZ_G5rbMISLtSbLoXmNDU9brV90d5y4UbTXcC' + 'version': 'J8rVTlBnjnUpuHYk3GUV56K4jK0m7uFmr1Q0qKsZnrgC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From abb7f63412c1df28384e0d4e7a8f7fb43122989d Mon Sep 17 00:00:00 2001 From: Soroush Yousefpour <46920873+gabrimatic@users.noreply.github.com> Date: Wed, 29 Jul 2026 07:45:42 +0200 Subject: [PATCH 091/147] Fix NestedScrollView example crash when switching tabs on desktop (#186993) Fixes https://github.com/flutter/flutter/issues/183199 The `NestedScrollView` API example that uses a `TabBar`/`TabBarView` throws on desktop when you scroll an inner list and then switch tabs. The inner `CustomScrollView`s are coordinated by the `NestedScrollView` through a shared controller, which can be attached to more than one `ScrollPosition` at a time (for example mid-transition between tabs). The automatic desktop scrollbar attaches to that shared controller, and a single scrollbar cannot represent multiple positions, so `RawScrollbar` throws the multiple-position assertion. This leaves the framework unchanged and fixes the example itself: the `TabBarView` body is wrapped in a `ScrollConfiguration` that disables the default scrollbars, since a single scrollbar cannot correctly represent the coordinated inner positions. A regression test reproduces the exact assertion from the issue (scroll, then a mouse-wheel pointer signal during a tab transition) and confirms it no longer throws. ## Tests - `flutter test examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart` - `flutter analyze examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart` ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../nested_scroll_view.0.dart | 127 ++++++++++-------- .../nested_scroll_view.0_test.dart | 49 +++++++ 2 files changed, 117 insertions(+), 59 deletions(-) diff --git a/examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart b/examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart index c88b9e985af47..400eb2e5f7158 100644 --- a/examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart +++ b/examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart @@ -64,66 +64,75 @@ class NestedScrollViewExample extends StatelessWidget { ), ]; }, - body: TabBarView( - // These are the contents of the tab views, below the tabs. - children: tabs.map((String name) { - return SafeArea( - top: false, - bottom: false, - child: Builder( - // This Builder is needed to provide a BuildContext that is - // "inside" the NestedScrollView, so that - // sliverOverlapAbsorberHandleFor() can find the - // NestedScrollView. - builder: (BuildContext context) { - return CustomScrollView( - // The "controller" and "primary" members should be left - // unset, so that the NestedScrollView can control this - // inner scroll view. - // If the "controller" property is set, then this scroll - // view will not be associated with the NestedScrollView. - // The PageStorageKey should be unique to this ScrollView; - // it allows the list to remember its scroll position when - // the tab view is not on the screen. - key: PageStorageKey(name), - slivers: [ - SliverOverlapInjector( - // This is the flip side of the SliverOverlapAbsorber - // above. - handle: - NestedScrollView.sliverOverlapAbsorberHandleFor( - context, - ), - ), - SliverPadding( - padding: const .all(8.0), - // In this example, the inner scroll view has - // fixed-height list items, hence the use of - // SliverFixedExtentList. However, one could use any - // sliver widget here, e.g. SliverList or SliverGrid. - sliver: SliverFixedExtentList.builder( - // The items in this example are fixed to 48 pixels - // high. This matches the Material Design spec for - // ListTile widgets. - itemExtent: 48.0, - // The itemCount of the SliverFixedExtentList.builder - // specifies how many children this inner list - // has. In this example, each tab has a list of - // exactly 30 items, but this is arbitrary. - itemCount: 30, - itemBuilder: (BuildContext context, int index) { - // This builder is called for each child. - // In this example, we just number each list item. - return ListTile(title: Text('Item $index')); - }, + body: ScrollConfiguration( + // Scrollbars have different default behaviors based on platform + // expectations. For the purpose of this sample, which can be run on + // any platform, default scrollbars are disabled for the inner + // scrollables. + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: TabBarView( + // These are the contents of the tab views, below the tabs. + children: tabs.map((String name) { + return SafeArea( + top: false, + bottom: false, + child: Builder( + // This Builder is needed to provide a BuildContext that is + // "inside" the NestedScrollView, so that + // sliverOverlapAbsorberHandleFor() can find the + // NestedScrollView. + builder: (BuildContext context) { + return CustomScrollView( + // The "controller" and "primary" members should be left + // unset, so that the NestedScrollView can control this + // inner scroll view. + // If the "controller" property is set, then this scroll + // view will not be associated with the NestedScrollView. + // The PageStorageKey should be unique to this ScrollView; + // it allows the list to remember its scroll position when + // the tab view is not on the screen. + key: PageStorageKey(name), + slivers: [ + SliverOverlapInjector( + // This is the flip side of the SliverOverlapAbsorber + // above. + handle: + NestedScrollView.sliverOverlapAbsorberHandleFor( + context, + ), ), - ), - ], - ); - }, - ), - ); - }).toList(), + SliverPadding( + padding: const .all(8.0), + // In this example, the inner scroll view has + // fixed-height list items, hence the use of + // SliverFixedExtentList. However, one could use any + // sliver widget here, e.g. SliverList or SliverGrid. + sliver: SliverFixedExtentList.builder( + // The items in this example are fixed to 48 pixels + // high. This matches the Material Design spec for + // ListTile widgets. + itemExtent: 48.0, + // The itemCount of the SliverFixedExtentList.builder + // specifies how many children this inner list + // has. In this example, each tab has a list of + // exactly 30 items, but this is arbitrary. + itemCount: 30, + itemBuilder: (BuildContext context, int index) { + // This builder is called for each child. + // In this example, we just number each list item. + return ListTile(title: Text('Item $index')); + }, + ), + ), + ], + ); + }, + ), + ); + }).toList(), + ), ), ), ), diff --git a/examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart b/examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart index 7768df8a97892..b183365ba4d00 100644 --- a/examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart +++ b/examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.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 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_api_samples/widgets/nested_scroll_view/nested_scroll_view.0.dart' as example; @@ -50,4 +51,52 @@ void main() { lessThan(initialAppBarHeight), ); }); + + testWidgets( + 'Does not crash when scrolling an inner list then switching tabs on desktop', + (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/183199 + // + // Without the ScrollConfiguration in this example, the default desktop + // scrollbar attaches to the NestedScrollView's coordinated controller and + // throws once more than one ScrollPosition is attached to it, which happens + // mid tab transition. + await tester.pumpWidget(const example.NestedScrollViewExampleApp()); + await tester.pumpAndSettle(); + + // The example opts out of the default scrollbars for its body. + expect(find.byType(Scrollbar), findsNothing); + + // Scroll the first tab's inner list. + await tester.drag( + find.text('Item 0'), + const Offset(0.0, -100.0), + touchSlopY: 0.0, + ); + await tester.pump(); + + // Begin, but do not finish, a tab transition so both tabs' inner scroll + // views are attached to the coordinated controller at the same time. + await tester.fling( + find.byType(TabBarView), + const Offset(-300.0, 0.0), + 800.0, + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 30)); + + // A mouse-wheel pointer signal mid-transition previously drove the + // scrollbar validation while more than one position was attached. + final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); + final Offset center = tester.getCenter(find.byType(TabBarView)); + await tester.sendEventToBinding(pointer.hover(center)); + await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 60.0))); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 700)); + await tester.pumpAndSettle(); + + expect(tester.takeException(), isNull); + }, + variant: TargetPlatformVariant.desktop(), + ); } From d776076fe2f7470f4da43cc6084137e5bbe35b6d Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 02:31:32 -0400 Subject: [PATCH 092/147] Roll Skia from 30ad01017a46 to 119cae396625 (2 revisions) (#190183) https://skia.googlesource.com/skia.git/+log/30ad01017a46..119cae396625 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll jsfiddle-base from 3c8958265e84 to 3b978e3a5600 2026-07-29 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0fccca2f3c599..9def0a19af436 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '30ad01017a46a31859b580bc907457b0e43907a8', + 'skia_revision': '119cae39662562fcd78054ff5bbef7a3ec5c26e2', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 89d96ef494ad6bd8e83a154018a0a338860570f9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 05:23:36 -0400 Subject: [PATCH 093/147] Roll Dart SDK from 49c637261348 to 1bbaacba6a5a (7 revisions) (#190196) https://dart.googlesource.com/sdk.git/+log/49c637261348..1bbaacba6a5a 2026-07-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-75.0.dev 2026-07-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-74.0.dev 2026-07-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-73.0.dev 2026-07-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-72.0.dev 2026-07-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-71.0.dev 2026-07-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-70.0.dev 2026-07-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-69.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC aaclarke@google.com,dart-vm-team@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 9def0a19af436..b01ec1582627d 100644 --- a/DEPS +++ b/DEPS @@ -56,12 +56,12 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '49c6372613486187900a553177de4af3faafb50c', + 'dart_revision': '1bbaacba6a5afcfe03b81a6a2244272b7e09731c', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': '9926156a583cec3d22d521232b31c70fa9a87dc1', - 'dart_boringssl_rev': 'e06e0ab4dead084b9269c8646fbdbe951cd3a21b', + 'dart_boringssl_rev': '22a0079b189c391b95689813a41982ce11876f0a', 'dart_core_rev': 'be0b1531c445a185d3e93887b8d0355fc766c314', 'dart_devtools_rev': '12d595649f189f1896722623f72599077f476848', 'dart_ecosystem_rev': '848b3bf3b757d2e9ae4d60030eeed5756c87783f', From 5a238f5812b38b924785a79448497b583006af38 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 29 Jul 2026 14:03:47 +0200 Subject: [PATCH 094/147] [Win32] Ignore mouse move events without capture when dragging (#190029) Fixes https://github.com/flutter/flutter/issues/189583 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../shell/platform/windows/flutter_window.cc | 17 +++++++-- .../windows/flutter_window_unittests.cc | 36 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_window.cc b/engine/src/flutter/shell/platform/windows/flutter_window.cc index 24398ee3cef42..c7d11e2ab8580 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window.cc @@ -692,9 +692,22 @@ FlutterWindow::HandleMessage(UINT const message, if (wparam & MK_SHIFT) { mods |= kShift; } + + // Mouse move with mouse buttons pressed but current HWND does not have + // capture. This can happen when switching windows mid drag - windows + // will stop honoring current capture and starts sending WM_MOUSEMOVE + // to the HWND below cursor, but without sending a WM_(LRMX)BUTTONDOWN + // first. This would confuse pointer tracking in Flutter so it is better + // to ignore these events. This also matches behavior of other + // applications where drag like this is ignored despite the mouse + // capture being lost. https://github.com/flutter/flutter/issues/189583 + auto buttons = ConvertWinMouseStateToFlutterButtons(wparam); + if (buttons != 0 && GetCapture() != window_handle_) { + break; + } + OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId, - ConvertWinMouseStateToFlutterButtons(wparam), - /*rotation=*/0, /*pressure=*/0, mods); + buttons, /*rotation=*/0, /*pressure=*/0, mods); } break; case WM_MOUSELEAVE: diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc index 3c3a3f0f488ca..741e47f4537c9 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_unittests.cc @@ -927,6 +927,42 @@ TEST_F(FlutterWindowTest, MouseButtonCaptureReleasedAfterAllButtonsReleased) { EXPECT_EQ(GetCapture(), nullptr); } +TEST_F(FlutterWindowTest, MouseMoveWithButtonIgnoredWithoutCapture) { + MockFlutterWindow win32window(100, 100); + MockWindowBindingHandlerDelegate delegate; + EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber()); + EXPECT_CALL(delegate, OnWindowStateEvent).Times(AnyNumber()); + win32window.SetView(&delegate); + + ReleaseCapture(); + ASSERT_EQ(GetCapture(), nullptr); + + EXPECT_CALL(delegate, OnPointerMove).Times(0); + win32window.InjectWindowMessage(WM_MOUSEMOVE, MK_LBUTTON, MAKELPARAM(10, 10)); +} + +TEST_F(FlutterWindowTest, MouseMoveWithButtonDispatchedWithCapture) { + MockFlutterWindow win32window(100, 100); + MockWindowBindingHandlerDelegate delegate; + EXPECT_CALL(win32window, OnWindowStateEvent).Times(AnyNumber()); + EXPECT_CALL(delegate, OnWindowStateEvent).Times(AnyNumber()); + win32window.SetView(&delegate); + + HWND window_handle = win32window.FlutterWindow::GetWindowHandle(); + ASSERT_NE(window_handle, nullptr); + SetCapture(window_handle); + ASSERT_EQ(GetCapture(), window_handle); + + EXPECT_CALL(delegate, + OnPointerMove(10.0, 10.0, kFlutterPointerDeviceKindMouse, + kDefaultPointerDeviceId, + kFlutterPointerButtonMousePrimary, 0, 0, 0)) + .Times(1); + win32window.InjectWindowMessage(WM_MOUSEMOVE, MK_LBUTTON, MAKELPARAM(10, 10)); + + ReleaseCapture(); +} + TEST_F(FlutterWindowTest, OnTouchPointerDown) { auto mock_proc_table = std::make_shared(); From 00e0a73384b86671457f091d3b327dc597cd3d1a Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 08:42:39 -0400 Subject: [PATCH 095/147] Roll Skia from 119cae396625 to 3ae9e364d30b (6 revisions) (#190208) https://skia.googlesource.com/skia.git/+log/119cae396625..3ae9e364d30b 2026-07-29 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from ff4855759c68 to c09abf1d3a9f (9 revisions) 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 42f146e4e61d to 1ec13b321130 (2 revisions) 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll bazel_build from 348e064d2951b8209... to 70733f74d415bf389... (1 revision) 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from a46a65d7ba05 to 3824c8346c38 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 10934785b43b to 92c4ae4cfeb2 (21 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index b01ec1582627d..1c7f071f87170 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '119cae39662562fcd78054ff5bbef7a3ec5c26e2', + 'skia_revision': '3ae9e364d30bc682ae137bb3def376dd364c1f75', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From e769fffb6c3346381b62693fe6ca16bee5ad2144 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 10:21:39 -0400 Subject: [PATCH 096/147] Roll Dart SDK from 1bbaacba6a5a to 43f7de5f4977 (1 revision) (#190211) https://dart.googlesource.com/sdk.git/+log/1bbaacba6a5a..43f7de5f4977 2026-07-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-76.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC aaclarke@google.com,dart-vm-team@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1c7f071f87170..fb2eeb7007ab3 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '1bbaacba6a5afcfe03b81a6a2244272b7e09731c', + 'dart_revision': '43f7de5f49777afc803a8fe69a752d3550f0602f', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 2e990feef7557a9e6db3dc656922b0bc020bad65 Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Wed, 29 Jul 2026 17:06:22 +0200 Subject: [PATCH 097/147] [web] Fix deferred loading with wasm (#190140) It seemed deferred loading via the recently added ([0]) `--enable-wasm-deferred-loading` never really worked. The embedder (flutter in this case) needs to invoke the callback function with either the wasm module bytes or a response object (which is a stream of bytes). Currently it invokes it with a promise of a response object which does not work atm. [0] flutter/flutter@80157ee66cdc2ffd0d61b3f8ca586a245c4fd040 --- dev/bots/suite_runners/run_web_tests.dart | 3 +++ .../lib/deferred_loading_lib.dart | 7 ++++++ .../deferred_loading_integration.dart | 25 +++++++++++++++++++ .../deferred_loading_integration_test.dart | 7 ++++++ .../flutter_js/src/entrypoint_loader.js | 5 +--- 5 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 dev/integration_tests/web_e2e_tests/lib/deferred_loading_lib.dart create mode 100644 dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration.dart create mode 100644 dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration_test.dart diff --git a/dev/bots/suite_runners/run_web_tests.dart b/dev/bots/suite_runners/run_web_tests.dart index 891f8224c0d7c..5551e97b51d56 100644 --- a/dev/bots/suite_runners/run_web_tests.dart +++ b/dev/bots/suite_runners/run_web_tests.dart @@ -178,6 +178,9 @@ class WebTestsSuite { useWasm: false, ), + () => _runWebE2eTest('deferred_loading_integration', buildMode: 'release', useWasm: false), + () => _runWebE2eTest('deferred_loading_integration', buildMode: 'release', useWasm: true), + () => _runWebTreeshakeTest(), () => _runFlutterDriverWebTest( diff --git a/dev/integration_tests/web_e2e_tests/lib/deferred_loading_lib.dart b/dev/integration_tests/web_e2e_tests/lib/deferred_loading_lib.dart new file mode 100644 index 0000000000000..703fc12362a12 --- /dev/null +++ b/dev/integration_tests/web_e2e_tests/lib/deferred_loading_lib.dart @@ -0,0 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +int getDeferredValue() { + return int.parse('42'); +} diff --git a/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration.dart b/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration.dart new file mode 100644 index 0000000000000..8628a6ce84186 --- /dev/null +++ b/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration.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 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:web_e2e_tests/deferred_loading_lib.dart' deferred as deferred_lib; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('deferred library loading works on web', (WidgetTester tester) async { + runApp(const MaterialApp(home: Scaffold(body: Text('Initial State')))); + await tester.pumpAndSettle(); + + // Load the deferred library and call code within it. + await deferred_lib.loadLibrary(); + + final int value = deferred_lib.getDeferredValue(); + expect(value, equals(42)); + await tester.pumpAndSettle(); + }); +} diff --git a/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration_test.dart b/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration_test.dart new file mode 100644 index 0000000000000..b2d2a1770b2fd --- /dev/null +++ b/dev/integration_tests/web_e2e_tests/test_driver/deferred_loading_integration_test.dart @@ -0,0 +1,7 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:integration_test/integration_test_driver.dart' as test; + +Future main() async => test.integrationDriver(); diff --git a/engine/src/flutter/lib/web_ui/flutter_js/src/entrypoint_loader.js b/engine/src/flutter/lib/web_ui/flutter_js/src/entrypoint_loader.js index 7a74451f246b0..1c131c596beb0 100644 --- a/engine/src/flutter/lib/web_ui/flutter_js/src/entrypoint_loader.js +++ b/engine/src/flutter/lib/web_ui/flutter_js/src/entrypoint_loader.js @@ -185,10 +185,7 @@ export class FlutterEntrypointLoader { const defaultLoadDeferredModules = (moduleNames, handleModule) => Promise.all( moduleNames.map((moduleName) => - handleModule( - moduleName, - fetch(resolveUrlWithSegments(entrypointBaseUrl, moduleName)) - ) + fetch(resolveUrlWithSegments(entrypointBaseUrl, moduleName)).then((response) => handleModule(moduleName, response)) ) ); const dartApp = await compiledDartApp.instantiate(await importsPromise, { From 2757a77a73df6a78eeb4a52637edcb12cc66d98b Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 12:41:15 -0400 Subject: [PATCH 098/147] Roll Skia from 3ae9e364d30b to 0f35bba4945c (2 revisions) (#190215) https://skia.googlesource.com/skia.git/+log/3ae9e364d30b..0f35bba4945c 2026-07-29 borenet@google.com Reapply "[infra] Remove all remaining references to gsutil" 2026-07-29 kjlubick@google.com Update fuzz docs If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index fb2eeb7007ab3..7deadb6fab880 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '3ae9e364d30bc682ae137bb3def376dd364c1f75', + 'skia_revision': '0f35bba4945c0450629ae46621dffc821c6d6ce3', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From ec66198385678c262df0886445a387772c646866 Mon Sep 17 00:00:00 2001 From: Lau Ching Jun Date: Wed, 29 Jul 2026 09:43:02 -0700 Subject: [PATCH 099/147] Split the platform and cpuArch part of TargetPlatform (#189479) Convert `enum TargetPlatform` into a class with two fields: `TargetPlatformType` and `CpuArch`. --- .../bin/fuchsia_asset_builder.dart | 2 +- .../lib/src/android/android_device.dart | 41 +- .../lib/src/android/android_emulator.dart | 1 + .../lib/src/android/gradle_utils.dart | 4 +- packages/flutter_tools/lib/src/artifacts.dart | 178 +++-- packages/flutter_tools/lib/src/asset.dart | 14 +- .../flutter_tools/lib/src/base/build.dart | 58 +- packages/flutter_tools/lib/src/base/dds.dart | 3 +- .../flutter_tools/lib/src/build_info.dart | 257 ++++---- .../lib/src/build_system/targets/android.dart | 18 +- .../lib/src/build_system/targets/assets.dart | 2 +- .../lib/src/build_system/targets/common.dart | 56 +- .../targets/dart_plugin_registrant.dart | 4 +- .../targets/icon_tree_shaker.dart | 4 +- .../lib/src/build_system/targets/ios.dart | 21 +- .../lib/src/build_system/targets/macos.dart | 35 +- .../build_system/targets/native_assets.dart | 4 +- .../lib/src/build_system/targets/web.dart | 12 +- .../build_system/tools/shader_compiler.dart | 30 +- .../lib/src/commands/assemble.dart | 38 +- .../lib/src/commands/build_bundle.dart | 28 +- .../lib/src/commands/build_ios.dart | 7 +- .../lib/src/commands/build_ios_framework.dart | 4 +- .../lib/src/commands/build_linux.dart | 6 +- .../src/commands/build_macos_framework.dart | 4 +- .../lib/src/commands/daemon.dart | 24 +- .../flutter_tools/lib/src/commands/run.dart | 21 +- .../flutter_tools/lib/src/commands/test.dart | 6 +- packages/flutter_tools/lib/src/compile.dart | 14 +- .../lib/src/custom_devices/custom_device.dart | 7 +- .../custom_devices/custom_device_config.dart | 10 +- .../flutter_tools/lib/src/darwin/darwin.dart | 9 +- packages/flutter_tools/lib/src/device.dart | 33 +- packages/flutter_tools/lib/src/emulator.dart | 1 + .../lib/src/flutter_application_package.dart | 28 +- .../flutter_tools/lib/src/ios/devices.dart | 3 - .../lib/src/ios/ios_emulators.dart | 1 + packages/flutter_tools/lib/src/ios/mac.dart | 4 +- .../flutter_tools/lib/src/ios/simulators.dart | 3 - .../lib/src/ios/xcode_build_settings.dart | 9 +- .../lib/src/isolated/devfs_web.dart | 6 +- .../isolated/native_assets/native_assets.dart | 45 +- .../src/isolated/native_assets/targets.dart | 77 +-- .../native_assets/test/native_assets.dart | 6 +- .../lib/src/isolated/resident_web_runner.dart | 12 +- .../lib/src/isolated/web_asset_server.dart | 2 +- .../lib/src/linux/build_linux.dart | 6 +- .../lib/src/linux/linux_device.dart | 10 - .../lib/src/macos/macos_device.dart | 3 - .../lib/src/macos/macos_ipad_device.dart | 3 - .../flutter_tools/lib/src/mdns_discovery.dart | 28 +- .../lib/src/proxied_devices/devices.dart | 2 +- .../lib/src/resident_runner.dart | 47 +- packages/flutter_tools/lib/src/run_hot.dart | 7 +- .../lib/src/runner/flutter_command.dart | 28 +- .../lib/src/test/flutter_web_platform.dart | 6 +- .../flutter_tools/lib/src/test/runner.dart | 2 +- .../lib/src/test/test_compiler.dart | 2 +- .../lib/src/test/web_test_compiler.dart | 4 +- .../lib/src/tester/flutter_tester.dart | 4 +- .../flutter_tools/lib/src/web/web_device.dart | 6 - .../lib/src/windows/build_windows.dart | 10 +- .../lib/src/windows/windows_device.dart | 7 +- .../commands.shard/hermetic/attach_test.dart | 6 +- .../hermetic/build_darwin_framework_test.dart | 4 +- .../hermetic/build_swift_package_test.dart | 18 +- .../hermetic/build_windows_test.dart | 2 +- .../commands.shard/hermetic/daemon_test.dart | 7 +- .../commands.shard/hermetic/doctor_test.dart | 607 ++++++++---------- .../commands.shard/hermetic/drive_test.dart | 6 +- .../commands.shard/hermetic/install_test.dart | 4 +- .../hermetic/proxied_devices_test.dart | 4 +- .../commands.shard/hermetic/run_test.dart | 24 +- .../hermetic/screenshot_command_test.dart | 79 ++- .../widget_preview/widget_preview_test.dart | 6 +- .../android/android_device_start_test.dart | 6 +- .../android/android_device_test.dart | 8 +- .../android/android_emulator_test.dart | 2 +- .../application_package_test.dart | 15 +- .../test/general.shard/artifacts_test.dart | 109 ++-- .../asset_bundle_flavors_test.dart | 2 +- .../asset_bundle_package_fonts_test.dart | 4 +- .../asset_bundle_package_test.dart | 12 +- .../test/general.shard/asset_bundle_test.dart | 76 +-- .../asset_bundle_variant_test.dart | 10 +- .../test/general.shard/asset_test.dart | 14 +- .../test/general.shard/base/build_test.dart | 59 +- .../test/general.shard/build_info_test.dart | 52 +- .../build_system/source_test.dart | 4 +- .../build_system/targets/android_test.dart | 21 +- .../build_system/targets/assets_test.dart | 6 +- .../build_system/targets/common_test.dart | 56 +- .../targets/deferred_components_test.dart | 6 +- .../targets/icon_tree_shaker_test.dart | 34 +- .../build_system/targets/ios_test.dart | 12 +- .../build_system/targets/linux_test.dart | 67 +- .../build_system/targets/macos_test.dart | 18 +- .../targets/shader_compiler_test.dart | 42 +- .../targets/web_dry_run_test.dart | 2 +- .../build_system/targets/web_test.dart | 4 +- .../build_system/targets/windows_test.dart | 24 +- .../general.shard/bundle_builder_test.dart | 12 +- .../test/general.shard/cold_test.dart | 4 +- .../compile_expression_test.dart | 2 +- .../test/general.shard/compile_test.dart | 12 +- .../custom_devices/custom_device_test.dart | 8 +- .../test/general.shard/darwin_test.dart | 14 +- .../general.shard/desktop_device_test.dart | 4 +- .../test/general.shard/device_test.dart | 4 +- .../drive/drive_service_test.dart | 2 +- .../drive/web_driver_service_test.dart | 2 +- .../test/general.shard/emulator_test.dart | 1 + .../general.shard/flutter_platform_test.dart | 6 +- .../test/general.shard/hot_shared.dart | 4 +- .../test/general.shard/hot_test.dart | 4 +- .../integration_test_device_test.dart | 2 +- .../isolated/android/native_assets_test.dart | 8 +- .../targets/native_assets_test.dart | 4 +- .../isolated/data_assets_test.dart | 4 +- .../isolated/ios/native_assets_test.dart | 4 +- .../isolated/linux/native_assets_test.dart | 14 +- .../isolated/macos/native_assets_test.dart | 8 +- .../isolated/native_assets_test.dart | 20 +- .../isolated/resident_runner_test.dart | 7 +- .../isolated/windows/native_assets_test.dart | 6 +- .../linux/linux_device_test.dart | 4 +- .../macos/macos_device_test.dart | 2 +- .../macos/macos_ipad_device_test.dart | 2 +- .../general.shard/mdns_discovery_test.dart | 2 +- .../resident_runner_helpers.dart | 13 +- .../general.shard/resident_runner_test.dart | 36 +- .../resident_web_runner_test.dart | 2 +- .../runner/target_devices_test.dart | 10 +- .../general.shard/terminal_handler_test.dart | 6 +- .../test/web_test_compiler_test.dart | 6 +- .../tester/flutter_tester_test.dart | 2 +- .../windows/build_windows_flavor_test.dart | 11 +- .../windows/windows_device_test.dart | 2 +- .../shader_compiler_test.dart | 6 +- .../web_plugin_registrant_test.dart | 4 +- .../flutter_tools/test/src/fake_devices.dart | 8 +- 141 files changed, 1435 insertions(+), 1596 deletions(-) diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart index d6ad78f49a6fb..1aaa82d1cc35d 100644 --- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart +++ b/packages/flutter_tools/bin/fuchsia_asset_builder.dart @@ -66,7 +66,7 @@ Future run(List args) async { packageConfigPath: argResults[_kOptionPackages] as String? ?? findPackageConfigFileOrDefault(globals.fs.currentDirectory).path, - targetPlatform: TargetPlatform.fuchsia_arm64, // This is not arch specific. + targetPlatform: const TargetPlatform(.fuchsia, .arm64), // This is not arch specific. ); if (assets == null) { diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 29cf0aef59a5d..0535322a6b113 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -190,16 +190,6 @@ class AndroidDevice extends Device { } } - @override - late final Future targetPlatform = () async { - return switch (await cpuArch) { - CpuArch.arm64 => TargetPlatform.android_arm64, - CpuArch.armv7 => TargetPlatform.android_arm, - CpuArch.x64 => TargetPlatform.android_x64, - CpuArch.x86 || CpuArch.riscv64 || CpuArch.unknown => TargetPlatform.unsupported, - }; - }(); - @override late final Future cpuArch = () async { // http://developer.android.com/ndk/guides/abis.html (x86, armeabi-v7a, ...) @@ -227,26 +217,7 @@ class AndroidDevice extends Device { @override Future supportsRuntimeMode(BuildMode buildMode) async { - switch (await targetPlatform) { - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - return buildMode != BuildMode.jitRelease; - case TargetPlatform.android: - case TargetPlatform.darwin: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.ios: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.linux_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: - case TargetPlatform.unsupported: - throw UnsupportedError('Invalid target platform for Android'); - } + return buildMode != .jitRelease; } @override @@ -873,13 +844,9 @@ class AndroidDevice extends Device { @override Future isSupported() async { - final TargetPlatform platform = await targetPlatform; - return switch (platform) { - TargetPlatform.android || - TargetPlatform.android_arm || - TargetPlatform.android_arm64 || - TargetPlatform.android_x64 => true, - _ => false, + return switch (await cpuArch) { + .arm64 || .armv7 || .x64 => true, + .x86 || .riscv64 || .unknown => false, }; } diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart index b8938b5e8c531..c72d3a7b33e99 100644 --- a/packages/flutter_tools/lib/src/android/android_emulator.dart +++ b/packages/flutter_tools/lib/src/android/android_emulator.dart @@ -13,6 +13,7 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/process.dart'; import '../base/utils.dart'; +import '../build_info.dart'; import '../device.dart'; import '../emulator.dart'; import 'android_sdk.dart'; diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 770e287a4d2f4..16fa52fd3210a 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -1191,13 +1191,13 @@ void updateLocalProperties({ if (buildInfo != null) { changeIfNecessary('flutter.buildMode', buildInfo.modeName); final String? buildName = validatedBuildNameForPlatform( - TargetPlatform.android_arm, + PlatformType.android, buildInfo.buildName ?? project.manifest.buildName, globals.logger, ); changeIfNecessary('flutter.versionName', buildName); final String? buildNumber = validatedBuildNumberForPlatform( - TargetPlatform.android_arm, + PlatformType.android, buildInfo.buildNumber ?? project.manifest.buildNumber, globals.logger, ); diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 2c4d4b731b535..e9ee816f28537 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -197,35 +197,16 @@ enum HostArtifact { // TODO(knopp): Remove once darwin artifacts are universal and moved out of darwin-x64 String _enginePlatformDirectoryName(TargetPlatform platform) { - if (platform == TargetPlatform.darwin) { + if (platform.type == .macos) { return 'darwin-x64'; } - return platform.getName(); -} - -// Remove android target platform type. -TargetPlatform? _mapTargetPlatform(TargetPlatform? targetPlatform) { - switch (targetPlatform) { - case TargetPlatform.android: - return TargetPlatform.android_arm64; - case TargetPlatform.ios: - case TargetPlatform.darwin: - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.unsupported: - case null: - return targetPlatform; + // iOS engine artifacts live in a single `ios` directory regardless of the + // target architecture, so the arch-qualified name from [getName] is not used + // here. + if (platform.type == .ios) { + return 'ios'; } + return platform.getName(); } class EngineBuildPaths { @@ -421,35 +402,34 @@ class CachedArtifacts implements Artifacts { BuildMode? mode, EnvironmentType? environmentType, }) { - platform = _mapTargetPlatform(platform); - switch (platform) { - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - assert(platform != TargetPlatform.android); - return _getAndroidArtifactPath(artifact, platform!, mode!); - case TargetPlatform.ios: + switch (platform?.type) { + case .android: + // A generic Android target has an unknown CPU architecture (e.g. the + // architecture-independent patched SDK requested when compiling the + // app's Dart kernel). Default to arm64 in that case, matching the + // historical behavior of the removed `_mapTargetPlatform` helper. + final TargetPlatform androidPlatform = platform!.cpuArch == .unknown + ? const TargetPlatform(.android, .arm64) + : platform; + return _getAndroidArtifactPath(artifact, androidPlatform, mode!); + case .ios: return _getIosArtifactPath(artifact, platform!, mode, environmentType); - case TargetPlatform.darwin: - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .macos: + case .linux: + case .windows: return _getDesktopArtifactPath(artifact, platform!, mode); - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: + case .fuchsia: return _getFuchsiaArtifactPath(artifact, platform!, mode!); - case TargetPlatform.tester: - case TargetPlatform.web_javascript: + case .tester: + case .web: + case .custom: case null: return _getHostArtifactPath( artifact, platform ?? _currentHostPlatform(_platform, _operatingSystemUtils), mode, ); - case TargetPlatform.unsupported: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -677,7 +657,11 @@ class CachedArtifacts implements Artifacts { case Artifact.genSnapshotX64: // For script snapshots any gen_snapshot binary will do. Returning gen_snapshot for // android_arm in profile mode because it is available on all supported host platforms. - return _getAndroidArtifactPath(artifact, TargetPlatform.android_arm, BuildMode.profile); + return _getAndroidArtifactPath( + artifact, + const TargetPlatform(.android, .armv7), + BuildMode.profile, + ); case Artifact.frontendServerSnapshotForEngineDartSdk: return _fileSystem.path.join( _dartSdkPath(_cache), @@ -785,13 +769,10 @@ class CachedArtifacts implements Artifacts { String? _getEngineArtifactsPath(TargetPlatform platform, [BuildMode? mode]) { final String engineDir = _cache.getArtifactDirectory('engine').path; final String platformName = _enginePlatformDirectoryName(platform); - switch (platform) { - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.darwin: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + switch (platform.type) { + case .linux: + case .macos: + case .windows: // TODO(zanderso): remove once debug desktop artifacts are uploaded // under a separate directory from the host artifacts. // https://github.com/flutter/flutter/issues/38935 @@ -800,23 +781,25 @@ class CachedArtifacts implements Artifacts { } final suffix = mode != BuildMode.debug ? '-${kebabCase(mode.cliName)}' : ''; return _fileSystem.path.join(engineDir, platformName + suffix); - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: + case .fuchsia: + case .tester: + case .web: assert(mode == null, 'Platform $platform does not support different build modes.'); return _fileSystem.path.join(engineDir, platformName); - case TargetPlatform.ios: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + case .ios: + assert(mode != null, 'Need to specify a build mode for platform $platform.'); + final suffix = mode != BuildMode.debug ? '-${kebabCase(mode!.cliName)}' : ''; + return _fileSystem.path.join(engineDir, platformName + suffix); + case .android: + assert( + platform.cpuArch != .unknown, + 'cannot use a generic Android platform to look up artifacts', + ); assert(mode != null, 'Need to specify a build mode for platform $platform.'); final suffix = mode != BuildMode.debug ? '-${kebabCase(mode!.cliName)}' : ''; return _fileSystem.path.join(engineDir, platformName + suffix); - case TargetPlatform.android: - assert(false, 'cannot use TargetPlatform.android to look up artifacts'); - return null; - case TargetPlatform.unsupported: + case .custom: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -826,20 +809,15 @@ class CachedArtifacts implements Artifacts { } TargetPlatform _currentHostPlatform(Platform platform, OperatingSystemUtils operatingSystemUtils) { + final cpuArch = CpuArch.fromHostPlatform(operatingSystemUtils.hostPlatform); if (platform.isMacOS) { - return TargetPlatform.darwin; + return TargetPlatform(.macos, cpuArch); } if (platform.isLinux) { - return switch (operatingSystemUtils.hostPlatform) { - HostPlatform.linux_x64 => TargetPlatform.linux_x64, - HostPlatform.linux_riscv64 => TargetPlatform.linux_riscv64, - _ => TargetPlatform.linux_arm64, - }; + return TargetPlatform(.linux, cpuArch); } if (platform.isWindows) { - return operatingSystemUtils.hostPlatform == HostPlatform.windows_arm64 - ? TargetPlatform.windows_arm64 - : TargetPlatform.windows_x64; + return TargetPlatform(.windows, cpuArch); } throw UnimplementedError('Host OS not supported.'); } @@ -1061,7 +1039,6 @@ class CachedLocalEngineArtifacts implements Artifacts { EnvironmentType? environmentType, }) { platform ??= _currentHostPlatform(_platform, _operatingSystemUtils); - platform = _mapTargetPlatform(platform); final isDirectoryArtifact = artifact == Artifact.flutterPatchedSdkPath; final String? artifactFileName = isDirectoryArtifact ? null @@ -1073,7 +1050,7 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.genSnapshotX64: return _genSnapshotPath(artifact); case Artifact.flutterTester: - return _flutterTesterPath(platform!); + return _flutterTesterPath(platform); case Artifact.isolateSnapshotData: case Artifact.vmSnapshotData: return _fileSystem.path.join( @@ -1089,7 +1066,7 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.flutterMacOSXcframework: return _fileSystem.path.join(localEngineInfo.targetOutPath, artifactFileName); case Artifact.platformKernelDill: - if (platform == TargetPlatform.fuchsia_x64 || platform == TargetPlatform.fuchsia_arm64) { + if (platform.type == .fuchsia) { return _fileSystem.path.join( localEngineInfo.targetOutPath, 'flutter_runner_patched_sdk', @@ -1122,7 +1099,7 @@ class CachedLocalEngineArtifacts implements Artifacts { // what was specified in [mode] argument because local engine will // have only one flutter_patched_sdk in standard location, that // is happen to be what debug(non-release) mode is using. - if (platform == TargetPlatform.fuchsia_x64 || platform == TargetPlatform.fuchsia_arm64) { + if (platform.type == .fuchsia) { return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter_runner_patched_sdk'); } return _getFlutterPatchedSdkPath(BuildMode.debug); @@ -1260,7 +1237,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts { BuildMode? mode, EnvironmentType? environmentType, }) { - if (platform == TargetPlatform.web_javascript) { + if (platform?.type == .web) { switch (artifact) { case Artifact.engineDartSdkPath: return _getDartSdkPath(); @@ -1467,7 +1444,7 @@ class _TestArtifacts implements Artifacts { final buffer = StringBuffer(); buffer.write(artifact); if (platform != null) { - buffer.write('.$platform'); + buffer.write('.${_enginePlatformDirectoryName(platform)}'); } if (mode != null) { buffer.write('.$mode'); @@ -1576,30 +1553,25 @@ String _getFlutterPrebuiltsPath(String baseOutPath, FileSystem fileSystem) { String _getPrebuiltTarget(Platform platform, OperatingSystemUtils operatingSystemUtils) { final TargetPlatform hostPlatform = _currentHostPlatform(platform, operatingSystemUtils); - switch (hostPlatform) { - case TargetPlatform.darwin: + switch (hostPlatform.type) { + case .macos: return 'macos-x64'; - case TargetPlatform.linux_riscv64: - return 'linux-riscv64'; - case TargetPlatform.linux_arm64: - return 'linux-arm64'; - case TargetPlatform.linux_x64: - return 'linux-x64'; - case TargetPlatform.windows_x64: - return 'windows-x64'; - case TargetPlatform.windows_arm64: - return 'windows-arm64'; - case TargetPlatform.ios: - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.web_javascript: - case TargetPlatform.tester: + case .linux: + return switch (hostPlatform.cpuArch) { + .riscv64 => 'linux-riscv64', + .arm64 => 'linux-arm64', + _ => 'linux-x64', + }; + case .windows: + return hostPlatform.cpuArch == .arm64 ? 'windows-arm64' : 'windows-x64'; + case .ios: + case .android: + case .fuchsia: + case .web: + case .tester: + case .custom: throwToolExit('Unsupported host platform: $hostPlatform'); - case TargetPlatform.unsupported: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 70af6b2afc9f1..b106b3ca277f2 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -356,7 +356,7 @@ class ManifestAssetBundle implements AssetBundle { transformers: const [], ); // Create .bin.json on web builds. - if (targetPlatform == TargetPlatform.web_javascript) { + if (targetPlatform.type == .web) { entries[_kAssetManifestBinJsonFilename] = AssetBundleEntry( DevFSStringContent('""'), kind: AssetKind.regular, @@ -660,7 +660,7 @@ class ManifestAssetBundle implements AssetBundle { _setIfChanged(_kAssetManifestBinFilename, assetManifestBinary, AssetKind.regular); // Create .bin.json on web builds. - if (targetPlatform == TargetPlatform.web_javascript) { + if (targetPlatform.type == .web) { final assetManifestBinaryJson = DevFSStringContent( json.encode(base64.encode(assetManifestBinary.bytes)), ); @@ -719,7 +719,7 @@ class ManifestAssetBundle implements AssetBundle { // On the web, don't compress the NOTICES file since the client doesn't have // dart:io to decompress it. So use the standard _setIfChanged to check if // the strings still match. - if (targetPlatform == TargetPlatform.web_javascript) { + if (targetPlatform.type == .web) { _setIfChanged(_kNoticeFile, DevFSStringContent(combinedLicenses), AssetKind.regular); return; } @@ -1398,7 +1398,11 @@ class ManifestAssetBundle implements AssetBundle { required Set platforms, required List transformers, }) { - _ensureAssetPathIsValid(assetsBaseDir: assetsBaseDir, assetUri: assetUri, packageName: packageName); + _ensureAssetPathIsValid( + assetsBaseDir: assetsBaseDir, + assetUri: assetUri, + packageName: packageName, + ); if (assetUri.pathSegments.first == 'packages' && !_fileSystem.isFileSync( _fileSystem.path.join(assetsBaseDir, _fileSystem.path.fromUri(assetUri)), @@ -1542,7 +1546,7 @@ class _Asset { } bool matchesPlatform(TargetPlatform targetPlatform) { - if (platforms.isEmpty || targetPlatform == TargetPlatform.tester) { + if (platforms.isEmpty || targetPlatform.type == .tester) { return true; } diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index d836785258489..55abebb0bc8fc 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -57,26 +57,18 @@ class GenSnapshot { Future run({ required SnapshotType snapshotType, - // TODO(chingjun): The [CpuArch] parameter is only used for iOS builds (to - // select the correct per-architecture gen_snapshot). This architecture - // information should instead be consolidated into [TargetPlatform] so that - // callers do not need to pass it separately. - CpuArch? cpuArch, Iterable additionalArgs = const [], }) { - assert(cpuArch != CpuArch.armv7); - assert(snapshotType.platform != TargetPlatform.ios || cpuArch != null); final args = [...additionalArgs]; // iOS and macOS have separate gen_snapshot binaries for each target - // architecture (iOS: armv7, arm64; macOS: x86_64, arm64). Select the right + // architecture (iOS: arm64; macOS: x86_64, arm64). Select the right // one for the target architecture in question. Artifact genSnapshotArtifact; - if (snapshotType.platform == TargetPlatform.ios || - snapshotType.platform == TargetPlatform.darwin) { - genSnapshotArtifact = cpuArch == CpuArch.arm64 - ? Artifact.genSnapshotArm64 - : Artifact.genSnapshotX64; + if (snapshotType.platform.type == .ios || snapshotType.platform.type == .macos) { + final CpuArch cpuArch = snapshotType.platform.cpuArch; + assert(cpuArch == .arm64 || cpuArch == .x64); + genSnapshotArtifact = cpuArch == .arm64 ? Artifact.genSnapshotArm64 : Artifact.genSnapshotX64; } else { genSnapshotArtifact = Artifact.genSnapshot; } @@ -117,15 +109,12 @@ class AOTSnapshotter { required BuildMode buildMode, required String mainPath, required String outputPath, - CpuArch? cpuArch, String? sdkRoot, List extraGenSnapshotOptions = const [], String? splitDebugInfo, required bool dartObfuscation, bool quiet = false, }) async { - assert(platform != TargetPlatform.ios || cpuArch != null); - if (!_isValidAotPlatform(platform, buildMode)) { _logger.printError('${platform.getName()} does not support AOT compilation.'); return 1; @@ -136,19 +125,14 @@ class AOTSnapshotter { final genSnapshotArgs = ['--deterministic']; - final bool targetingApplePlatform = - platform == TargetPlatform.ios || platform == TargetPlatform.darwin; + final bool targetingApplePlatform = platform.type == .ios || platform.type == .macos; _logger.printTrace('targetingApplePlatform = $targetingApplePlatform'); final bool extractAppleDebugSymbols = buildMode == BuildMode.profile || buildMode == BuildMode.release; _logger.printTrace('extractAppleDebugSymbols = $extractAppleDebugSymbols'); - final bool targetingAndroidPlatform = - platform == TargetPlatform.android || - platform == TargetPlatform.android_arm || - platform == TargetPlatform.android_arm64 || - platform == TargetPlatform.android_x64; + final targetingAndroidPlatform = platform.type == .android; _logger.printTrace('targetingAndroidPlatform = $targetingAndroidPlatform'); // We strip snapshot by default, but allow to suppress this behavior @@ -172,7 +156,7 @@ class AOTSnapshotter { // library that the end-developer can link into their app. const frameworkName = 'App.framework'; if (!quiet) { - final String targetArch = cpuArch!.darwinArchName; + final String targetArch = platform.cpuArch.darwinArchName; _logger.printStatus('Building $frameworkName for $targetArch...'); } frameworkPath = _fileSystem.path.join(outputPath, frameworkName); @@ -184,7 +168,7 @@ class AOTSnapshotter { // When the minimum version is updated, remember to update // template MinimumOSVersion. // https://github.com/flutter/flutter/pull/62902 - final minOSVersion = platform == TargetPlatform.ios + final minOSVersion = platform.type == .ios ? FlutterDarwinPlatform.ios.deploymentTarget().toString() : FlutterDarwinPlatform.macos.deploymentTarget().toString(); genSnapshotArgs.addAll([ @@ -218,7 +202,7 @@ class AOTSnapshotter { } } - if (platform == TargetPlatform.android_arm) { + if (platform == const TargetPlatform(.android, .armv7)) { // Use softfp for Android armv7 devices. // TODO(cbracken): eliminate this when we fix https://github.com/flutter/flutter/issues/17489 genSnapshotArgs.add('--no-sim-use-hardfp'); @@ -229,8 +213,9 @@ class AOTSnapshotter { // The name of the debug file must contain additional information about // the architecture, since a single build command may produce - // multiple debug files. - final String archName = platform.getName(cpuArch: cpuArch); + // multiple debug files. [TargetPlatform.getName] already includes the + // architecture for the platforms that need it (e.g. `ios-arm64`). + final String archName = platform.getName(); final debugFilename = 'app.$archName.symbols'; final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; if (shouldSplitDebugInfo) { @@ -253,7 +238,6 @@ class AOTSnapshotter { final int genSnapshotExitCode = await _genSnapshot.run( snapshotType: snapshotType, additionalArgs: genSnapshotArgs, - cpuArch: cpuArch, ); if (genSnapshotExitCode != 0) { _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); @@ -301,17 +285,9 @@ class AOTSnapshotter { if (buildMode == BuildMode.debug) { return false; } - return const [ - TargetPlatform.android_arm, - TargetPlatform.android_arm64, - TargetPlatform.android_x64, - TargetPlatform.ios, - TargetPlatform.darwin, - TargetPlatform.linux_x64, - TargetPlatform.linux_arm64, - TargetPlatform.linux_riscv64, - TargetPlatform.windows_x64, - TargetPlatform.windows_arm64, - ].contains(platform); + return switch (platform.type) { + .android || .ios || .macos || .linux || .windows => true, + _ => false, + }; } } diff --git a/packages/flutter_tools/lib/src/base/dds.dart b/packages/flutter_tools/lib/src/base/dds.dart index e498bdef6028f..a8889ee9864e0 100644 --- a/packages/flutter_tools/lib/src/base/dds.dart +++ b/packages/flutter_tools/lib/src/base/dds.dart @@ -10,7 +10,6 @@ import 'package:dds/dds_launcher.dart'; import 'package:meta/meta.dart'; import '../artifacts.dart'; -import '../build_info.dart'; import '../device.dart'; import '../globals.dart' as globals; import '../resident_runner.dart'; @@ -267,7 +266,7 @@ mixin DartDevelopmentServiceLocalOperationsMixin { if (!(await _waitForExtensionsForDevice(device, method))) { return; } - if (device.targetPlatform == TargetPlatform.web_javascript) { + if (device.targetPlatform.type == .web) { await device.vmService!.callMethodWrapper(method, args: params); return; } diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 551da4d7bc108..ed83b8be191a7 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -513,14 +513,14 @@ enum BuildMode { enum EnvironmentType { physical, simulator } String? validatedBuildNumberForPlatform( - TargetPlatform targetPlatform, + PlatformType platformType, String? buildNumber, Logger logger, ) { if (buildNumber == null) { return null; } - if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { + if (platformType == .ios || platformType == .macos) { // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final disallowed = RegExp(r'[^\d\.]'); String tmpBuildNumber = buildNumber.replaceAll(disallowed, ''); @@ -543,9 +543,7 @@ String? validatedBuildNumberForPlatform( } return tmpBuildNumber; } - if (targetPlatform == TargetPlatform.android_arm || - targetPlatform == TargetPlatform.android_arm64 || - targetPlatform == TargetPlatform.android_x64) { + if (platformType == .android) { // See versionCode at https://developer.android.com/studio/publish/versioning final disallowed = RegExp(r'[^\d]'); String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, ''); @@ -565,15 +563,11 @@ String? validatedBuildNumberForPlatform( return buildNumber; } -String? validatedBuildNameForPlatform( - TargetPlatform targetPlatform, - String? buildName, - Logger logger, -) { +String? validatedBuildNameForPlatform(PlatformType platformType, String? buildName, Logger logger) { if (buildName == null) { return null; } - if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { + if (platformType == .ios || platformType == .macos) { // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final disallowed = RegExp(r'[^\d\.]'); String tmpBuildName = buildName.replaceAll(disallowed, ''); @@ -596,13 +590,7 @@ String? validatedBuildNameForPlatform( } return tmpBuildName; } - if (targetPlatform == TargetPlatform.android || - targetPlatform == TargetPlatform.android_arm || - targetPlatform == TargetPlatform.android_arm64 || - targetPlatform == TargetPlatform.android_x64) { - // See versionName at https://developer.android.com/studio/publish/versioning - return buildName; - } + // See versionName at https://developer.android.com/studio/publish/versioning for Android. return buildName; } @@ -695,109 +683,162 @@ enum CpuArch { }; } -enum TargetPlatform { - android('android'), - ios('ios'), - darwin('darwin'), - linux_x64('linux-x64'), - linux_arm64('linux-arm64'), - linux_riscv64('linux-riscv64'), - windows_x64('windows-x64'), - windows_arm64('windows-arm64'), - fuchsia_arm64('fuchsia-arm64'), - fuchsia_x64('fuchsia-x64'), - tester('flutter-tester'), - web_javascript('web-javascript'), - // The arch specific android target platforms are soft-deprecated. - // Instead of using TargetPlatform as a combination arch + platform - // the code will be updated to carry arch information in [CpuArch]. - android_arm('android-arm'), - android_arm64('android-arm64'), - android_x64('android-x64'), - unsupported('unsupported'); - - const TargetPlatform(this._defaultName); +/// The type of platform (OS / runtime family) that a target or device +/// represents. +/// +/// This is combined with a [CpuArch] to form a [TargetPlatform]. +enum PlatformType { + web, + android, + ios, + linux, + macos, + windows, + fuchsia, + custom, + tester, + unsupported; + + @override + String toString() => name; + + static PlatformType? fromString(String platformType) => values.asNameMap()[platformType]; +} + +/// The platform a Flutter application is built for. +/// +/// A [TargetPlatform] is the combination of a [PlatformType] (the OS / runtime +/// family, e.g. Android or macOS) and a [CpuArch] (the CPU architecture, e.g. +/// arm64). +/// +/// Code that needs to branch on the platform should generally inspect [type] +/// and/or [cpuArch]. +@immutable +final class TargetPlatform { + const TargetPlatform(this.type, this.cpuArch); factory TargetPlatform.fromName(String name) { return switch (name) { - 'android' => TargetPlatform.android, - 'android-arm' => TargetPlatform.android_arm, - 'android-arm64' => TargetPlatform.android_arm64, - 'android-x64' => TargetPlatform.android_x64, - 'fuchsia-arm64' => TargetPlatform.fuchsia_arm64, - 'fuchsia-x64' => TargetPlatform.fuchsia_x64, - 'ios' => TargetPlatform.ios, - // For backward-compatibility and also for Tester, where it must match - // host platform name (HostPlatform.darwin_x64) - 'darwin' || 'darwin-x64' || 'darwin-arm64' => TargetPlatform.darwin, - 'linux-x64' => TargetPlatform.linux_x64, - 'linux-arm64' => TargetPlatform.linux_arm64, - 'linux-riscv64' => TargetPlatform.linux_riscv64, - 'windows-x64' => TargetPlatform.windows_x64, - 'windows-arm64' => TargetPlatform.windows_arm64, - 'web-javascript' => TargetPlatform.web_javascript, - 'flutter-tester' => TargetPlatform.tester, + 'android' => const TargetPlatform(.android, .unknown), + 'android-arm' => const TargetPlatform(.android, .armv7), + 'android-arm64' => const TargetPlatform(.android, .arm64), + 'android-x64' => const TargetPlatform(.android, .x64), + 'fuchsia-arm64' => const TargetPlatform(.fuchsia, .arm64), + 'fuchsia-x64' => const TargetPlatform(.fuchsia, .x64), + // `ios` is architecture-agnostic and defaults to arm64; `ios-arm64` and + // `ios-x64` (simulator) name a specific architecture. + 'ios' || 'ios-arm64' => const TargetPlatform(.ios, .arm64), + 'ios-x64' => const TargetPlatform(.ios, .x64), + 'ios-armv7' => const TargetPlatform(.ios, .armv7), + // `darwin` is architecture-agnostic and defaults to arm64 (Apple + // Silicon); `darwin-x64` and `darwin-arm64` name a specific architecture. + 'darwin' || 'darwin-arm64' => const TargetPlatform(.macos, .arm64), + 'darwin-x64' => const TargetPlatform(.macos, .x64), + 'linux-x64' => const TargetPlatform(.linux, .x64), + 'linux-arm64' => const TargetPlatform(.linux, .arm64), + 'linux-riscv64' => const TargetPlatform(.linux, .riscv64), + 'windows-x64' => const TargetPlatform(.windows, .x64), + 'windows-arm64' => const TargetPlatform(.windows, .arm64), + 'web-javascript' => const TargetPlatform(.web, .unknown), + 'flutter-tester' => const TargetPlatform(.tester, .unknown), _ => throw Exception('Unsupported platform name "$name"'), }; } - final String _defaultName; + /// The platform type (OS / runtime family). + final PlatformType type; - String getName({CpuArch? cpuArch}) { - return switch (this) { - TargetPlatform.ios when cpuArch != null => 'ios-${cpuArch.darwinArchName}', - TargetPlatform.darwin when cpuArch != null => 'darwin-${cpuArch.darwinArchName}', - _ => _defaultName, + /// The CPU architecture of the target. + /// + /// This is [CpuArch.unknown] only when a CPU architecture is not applicable + /// (e.g. web) or has not yet been resolved (e.g. a generic Android target). + final CpuArch cpuArch; + + /// The canonical set of known target platforms. + /// + /// This mirrors the values that used to exist when [TargetPlatform] was an + /// enum, and is primarily useful for tests that need to iterate over all + /// known platforms. + static const List values = [ + TargetPlatform(.android, .unknown), + TargetPlatform(.ios, .arm64), + TargetPlatform(.macos, .arm64), + TargetPlatform(.linux, .x64), + TargetPlatform(.linux, .arm64), + TargetPlatform(.linux, .riscv64), + TargetPlatform(.windows, .x64), + TargetPlatform(.windows, .arm64), + TargetPlatform(.fuchsia, .arm64), + TargetPlatform(.fuchsia, .x64), + TargetPlatform(.tester, .unknown), + TargetPlatform(.web, .unknown), + TargetPlatform(.android, .armv7), + TargetPlatform(.android, .arm64), + TargetPlatform(.android, .x64), + TargetPlatform(.unsupported, .unknown), + ]; + + @override + bool operator ==(Object other) => + other is TargetPlatform && other.type == type && other.cpuArch == cpuArch; + + @override + int get hashCode => Object.hash(type, cpuArch); + + /// The canonical string name for this target platform. + /// + /// The name generally follows the `-` convention (e.g. + /// `linux-x64`, `android-arm64`, `ios-arm64`, `darwin-x64`), with a few + /// historical exceptions retained for backward compatibility (e.g. the macOS + /// platform is named `darwin`, and `flutter-tester`). When the architecture + /// is not known ([CpuArch.unknown]), the bare platform name is used (e.g. + /// `ios`, `darwin`, `android`). + String getName() { + return switch (type) { + .android => cpuArch == .unknown ? 'android' : cpuArch.androidPlatformName, + .ios => cpuArch == .unknown ? 'ios' : 'ios-${cpuArch.dartName}', + .macos => cpuArch == .unknown ? 'darwin' : 'darwin-${cpuArch.dartName}', + .linux || .windows || .fuchsia => '${type.name}-${cpuArch.dartName}', + .tester => 'flutter-tester', + .web => 'web-javascript', + .unsupported => 'unsupported', + .custom => throw UnsupportedError('Unexpected target platform $this'), }; } - String get fuchsiaArchForTargetPlatform => switch (this) { - fuchsia_arm64 => 'arm64', - fuchsia_x64 => 'x64', - android || - android_arm || - android_arm64 || - android_x64 || - darwin || - ios || - linux_arm64 || - linux_riscv64 || - linux_x64 || - tester || - web_javascript || - windows_x64 || - windows_arm64 || - unsupported => throw UnsupportedError('Unexpected Fuchsia platform $this'), + /// The platform name used to identify a device, e.g. in the `flutter devices` + /// output, the daemon protocol, and analytics. + /// + /// This is like [getName], but omits the CPU architecture for iOS and macOS, + /// preserving the historical device platform identifiers (`ios`, `darwin`). + /// The architecture of a device is reported separately (e.g. via the device's + /// `cpuArch`), so it is not duplicated here. + String get devicePlatformName => switch (type) { + .ios => 'ios', + .macos => 'darwin', + _ => getName(), }; - String get osName => switch (this) { - linux_x64 || linux_arm64 || linux_riscv64 => 'linux', - darwin => 'macos', - windows_x64 || windows_arm64 => 'windows', - android || android_arm || android_arm64 || android_x64 => 'android', - fuchsia_arm64 || fuchsia_x64 => 'fuchsia', - ios => 'ios', - tester => 'flutter-tester', - web_javascript => 'web', - unsupported => throw UnsupportedError('Unexpected target platform $this'), + String get fuchsiaArchForTargetPlatform => switch (type) { + .fuchsia => cpuArch == .arm64 ? 'arm64' : 'x64', + _ => throw UnsupportedError('Unexpected Fuchsia platform $this'), }; - String get simpleName => switch (this) { - linux_x64 || darwin || windows_x64 => 'x64', - linux_arm64 || windows_arm64 => 'arm64', - linux_riscv64 => 'riscv64', - android || - android_arm || - android_arm64 || - android_x64 || - fuchsia_arm64 || - fuchsia_x64 || - ios || - tester || - web_javascript || - unsupported => throw UnsupportedError('Unexpected target platform $this'), + String get osName => switch (type) { + .linux => 'linux', + .macos => 'macos', + .windows => 'windows', + .android => 'android', + .fuchsia => 'fuchsia', + .ios => 'ios', + .tester => 'flutter-tester', + .web => 'web', + .custom || .unsupported => throw UnsupportedError('Unexpected target platform $this'), }; + @override + String toString() => getName(); + static Never throwUnsupportedTarget() => throw UnsupportedError('Target platform is unsupported.'); } @@ -951,7 +992,7 @@ String getWebBuildDirectory() { String getLinuxBuildDirectory([TargetPlatform? targetPlatform, String? flavor]) { final String arch = (targetPlatform == null) ? _getCurrentHostPlatformArchName() - : targetPlatform.simpleName; + : targetPlatform.cpuArch.dartName; final String subDirs = (flavor != null && flavor.isNotEmpty) ? globals.fs.path.join('linux', arch, flavor) : globals.fs.path.join('linux', arch); @@ -963,7 +1004,7 @@ String getLinuxBuildDirectory([TargetPlatform? targetPlatform, String? flavor]) /// When [flavor] is non-empty, a `/` segment is inserted so that /// different flavors can coexist on disk without overwriting each other. String getWindowsBuildDirectory(TargetPlatform targetPlatform, [String? flavor]) { - final String arch = targetPlatform.simpleName; + final String arch = targetPlatform.cpuArch.dartName; final String subDirs = (flavor != null && flavor.isNotEmpty) ? globals.fs.path.join('windows', arch, flavor) : globals.fs.path.join('windows', arch); @@ -1222,8 +1263,8 @@ String? _uncapitalize(String? s) { // flutter_ignore: deprecation_syntax (see analyze.dart) @Deprecated('Use TargetPlatform.getName() instead') -String getNameForTargetPlatform(TargetPlatform platform, {CpuArch? cpuArch}) { - return platform.getName(cpuArch: cpuArch); +String getNameForTargetPlatform(TargetPlatform platform) { + return platform.getName(); } // flutter_ignore: deprecation_syntax (see analyze.dart) diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index 76c2c31fc15dc..8d90f76f23f59 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -77,7 +77,7 @@ abstract class AndroidAssetBundle extends Target { environment, outputDirectory, dartHookResult: dartHookResult, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), buildMode: buildMode, flavor: environment.defines[kFlavor], additionalContent: { @@ -141,7 +141,7 @@ class ProfileAndroidApplication extends CopyFlutterAotBundle { @override List get dependencies => const [ - AotElfProfile(TargetPlatform.android_arm), + AotElfProfile(TargetPlatform(.android, .armv7)), AotAndroidAssetBundle(), ]; } @@ -155,7 +155,7 @@ class ReleaseAndroidApplication extends CopyFlutterAotBundle { @override List get dependencies => const [ - AotElfRelease(TargetPlatform.android_arm), + AotElfRelease(TargetPlatform(.android, .armv7)), AotAndroidAssetBundle(), ]; } @@ -287,12 +287,12 @@ class AndroidAot extends AotElfBase { } // AndroidAot instances used by the bundle rules below. -const androidArmProfile = AndroidAot(TargetPlatform.android_arm, BuildMode.profile); -const androidArm64Profile = AndroidAot(TargetPlatform.android_arm64, BuildMode.profile); -const androidx64Profile = AndroidAot(TargetPlatform.android_x64, BuildMode.profile); -const androidArmRelease = AndroidAot(TargetPlatform.android_arm, BuildMode.release); -const androidArm64Release = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); -const androidx64Release = AndroidAot(TargetPlatform.android_x64, BuildMode.release); +const androidArmProfile = AndroidAot(TargetPlatform(.android, .armv7), BuildMode.profile); +const androidArm64Profile = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.profile); +const androidx64Profile = AndroidAot(TargetPlatform(.android, .x64), BuildMode.profile); +const androidArmRelease = AndroidAot(TargetPlatform(.android, .armv7), BuildMode.release); +const androidArm64Release = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); +const androidx64Release = AndroidAot(TargetPlatform(.android, .x64), BuildMode.release); /// A rule paired with [AndroidAot] that copies the produced so file and manifest.json (if present) into the output directory. class AndroidAotBundle extends Target { 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 dcbe48c2a6015..12f73fa789b9e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -295,7 +295,7 @@ class CopyAssets extends Target { @override Future build( Environment environment, { - TargetPlatform targetPlatform = TargetPlatform.android, + TargetPlatform targetPlatform = const TargetPlatform(.android, .unknown), }) async { final String? buildModeEnvironment = environment.defines[kBuildMode]; if (buildModeEnvironment == null) { 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 b66db673bcc2e..948a573c97a59 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -92,7 +92,7 @@ class CopyFlutterBundle extends Target { environment, environment.outputDir, dartHookResult: dartHookResult, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), buildMode: buildMode, flavor: flavor, additionalContent: { @@ -229,50 +229,28 @@ class KernelSnapshot extends Target { final String? fileSystemScheme = environment.defines[kFileSystemScheme]; TargetModel targetModel = TargetModel.flutter; - if (targetPlatform == TargetPlatform.fuchsia_x64 || - targetPlatform == TargetPlatform.fuchsia_arm64) { + if (targetPlatform.type == .fuchsia) { targetModel = TargetModel.flutterRunner; } // Force linking of the platform for desktop embedder targets since these // do not correctly load the core snapshots in debug mode. // See https://github.com/flutter/flutter/issues/44724 - final bool forceLinkPlatform; - switch (targetPlatform) { - case TargetPlatform.darwin: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: - case TargetPlatform.linux_x64: - forceLinkPlatform = true; - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.ios: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - forceLinkPlatform = false; - case TargetPlatform.unsupported: - TargetPlatform.throwUnsupportedTarget(); - } + final bool forceLinkPlatform = switch (targetPlatform.type) { + .macos || .windows => true, + .linux => targetPlatform.cpuArch == .x64, + .unsupported => TargetPlatform.throwUnsupportedTarget(), + .web || .android || .ios || .fuchsia || .custom || .tester => false, + }; - final String? targetOS = switch (targetPlatform) { - TargetPlatform.fuchsia_arm64 || TargetPlatform.fuchsia_x64 => 'fuchsia', - TargetPlatform.android || - TargetPlatform.android_arm || - TargetPlatform.android_arm64 || - TargetPlatform.android_x64 => 'android', - TargetPlatform.darwin => 'macos', - TargetPlatform.ios => 'ios', - TargetPlatform.linux_arm64 || - TargetPlatform.linux_riscv64 || - TargetPlatform.linux_x64 => 'linux', - TargetPlatform.windows_arm64 || TargetPlatform.windows_x64 => 'windows', - TargetPlatform.tester || TargetPlatform.web_javascript => null, - TargetPlatform.unsupported => TargetPlatform.throwUnsupportedTarget(), + final String? targetOS = switch (targetPlatform.type) { + .fuchsia => 'fuchsia', + .android => 'android', + .macos => 'macos', + .ios => 'ios', + .linux => 'linux', + .windows => 'windows', + .tester || .web || .custom => null, + .unsupported => TargetPlatform.throwUnsupportedTarget(), }; final PackageConfig packageConfig = await loadPackageConfigWithLogging( diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart index 26b717f5cad88..64064d60bba13 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart @@ -56,9 +56,7 @@ class DartPluginRegistrantTarget extends Target { // code should just do the right thing on every platform. // Failing that, consider throwing if `targetPlatform` isn't set and finding // all violations, as it's not consistently set here. - return targetPlatform == TargetPlatform.fuchsia_arm64 || - targetPlatform == TargetPlatform.fuchsia_x64 || - targetPlatform == TargetPlatform.web_javascript; + return targetPlatform?.type == .fuchsia || targetPlatform?.type == .web; } @override 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 aeefa12a0c54e..16f707a32aa9e 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 @@ -140,9 +140,7 @@ class IconTreeShaker { } // Add space as an optional code point, as web uses it to measure the font height. - final optionalCodePoints = _targetPlatform == TargetPlatform.web_javascript - ? [kSpacePoint] - : []; + final optionalCodePoints = _targetPlatform.type == .web ? [kSpacePoint] : []; result[entry.value] = _IconTreeShakerData( family: entry.key, relativePath: entry.value, 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 9535489b8f46d..4034fe786cdb9 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -75,7 +75,7 @@ abstract class AotAssemblyBase extends Target { final List cpuArchs = environment.defines[kIosArchs]?.split(' ').map(getCpuArchForName).toList() ?? [CpuArch.arm64]; - if (targetPlatform != TargetPlatform.ios) { + if (targetPlatform.type != .ios) { throw Exception('aot_assembly is only supported for iOS applications.'); } @@ -108,11 +108,10 @@ abstract class AotAssemblyBase extends Target { } pending.add( snapshotter.build( - platform: targetPlatform, + platform: TargetPlatform(.ios, cpuArch), buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), - cpuArch: cpuArch, sdkRoot: sdkRoot, quiet: true, splitDebugInfo: splitDebugInfo, @@ -163,7 +162,7 @@ class AotAssemblyRelease extends AotAssemblyBase { // it resolves to a file (ios/gen_snapshot) that never exists. This was // split into gen_snapshot_arm64 and gen_snapshot_armv7. // Source.artifact(Artifact.genSnapshot, - // platform: TargetPlatform.ios, + // platform: TargetPlatform(.ios, .arm64), // mode: BuildMode.release, // ), ]; @@ -192,7 +191,7 @@ class AotAssemblyProfile extends AotAssemblyBase { // it resolves to a file (ios/gen_snapshot) that never exists. This was // split into gen_snapshot_arm64 and gen_snapshot_armv7. // Source.artifact(Artifact.genSnapshot, - // platform: TargetPlatform.ios, + // platform: TargetPlatform(.ios, .arm64), // mode: BuildMode.profile, // ), ]; @@ -250,7 +249,11 @@ abstract class UnpackIOS extends UnpackDarwin { const Source.pattern( '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart', ), - Source.artifact(Artifact.flutterXcframework, platform: TargetPlatform.ios, mode: buildMode), + Source.artifact( + Artifact.flutterXcframework, + platform: darwinPlatform.targetPlatform, + mode: buildMode, + ), ]; @override @@ -285,7 +288,7 @@ abstract class UnpackIOS extends UnpackDarwin { environment, environmentType: environmentType, framework: Artifact.flutterFramework, - targetPlatform: TargetPlatform.ios, + targetPlatform: darwinPlatform.targetPlatform, buildMode: buildMode, ); await _copyFrameworkDysm(environment, sdkRoot: sdkRoot, environmentType: environmentType); @@ -311,7 +314,7 @@ abstract class UnpackIOS extends UnpackDarwin { final Directory frameworkDsym = environment.fileSystem.directory( environment.artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: TargetPlatform.ios, + platform: darwinPlatform.targetPlatform, mode: buildMode, environmentType: environmentType, ), @@ -715,7 +718,7 @@ abstract class IosAssetBundle extends Target { environment, assetDirectory, dartHookResult: dartHookResult, - targetPlatform: TargetPlatform.ios, + targetPlatform: FlutterDarwinPlatform.ios.targetPlatform, buildMode: buildMode, additionalInputs: [ flutterProject.ios.infoPlist, 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 e42533a8ddfba..ff95a1ca31b25 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -140,7 +140,7 @@ class ReleaseUnpackMacOS extends UnpackMacOS { final Directory frameworkDsym = environment.fileSystem.directory( environment.artifacts.getArtifactPath( Artifact.flutterMacOSFrameworkDsym, - platform: TargetPlatform.darwin, + platform: darwinPlatform.targetPlatform, mode: buildMode, ), ); @@ -288,7 +288,7 @@ class CompileMacOSFramework extends Target { ); final targetPlatform = TargetPlatform.fromName(targetPlatformEnvironment); final List cpuArchs = getCpuArchsFromEnv(environment.defines); - if (targetPlatform != TargetPlatform.darwin) { + if (targetPlatform.type != .macos) { throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.'); } @@ -321,8 +321,7 @@ class CompileMacOSFramework extends Target { buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), - platform: TargetPlatform.darwin, - cpuArch: cpuArch, + platform: TargetPlatform(.macos, cpuArch), splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, extraGenSnapshotOptions: extraGenSnapshotOptions, @@ -359,10 +358,16 @@ class CompileMacOSFramework extends Target { List get dependencies => const [KernelSnapshot()]; @override - List get inputs => const [ - Source.pattern('{BUILD_DIR}/app.dill'), - Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'), - Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin), + List get inputs => [ + const Source.pattern('{BUILD_DIR}/app.dill'), + const Source.pattern( + '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart', + ), + Source.artifact( + Artifact.genSnapshot, + mode: BuildMode.release, + platform: FlutterDarwinPlatform.macos.targetPlatform, + ), ]; @override @@ -451,7 +456,7 @@ abstract class MacOSBundleFlutterAssets extends Target { environment, assetDirectory, dartHookResult: dartHookResult, - targetPlatform: TargetPlatform.darwin, + targetPlatform: FlutterDarwinPlatform.macos.targetPlatform, buildMode: buildMode, flavor: flavor, additionalContent: { @@ -505,12 +510,12 @@ abstract class MacOSBundleFlutterAssets extends Target { try { final String vmSnapshotData = environment.artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: BuildMode.debug, ); final String isolateSnapshotData = environment.artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: BuildMode.debug, ); environment.fileSystem @@ -582,14 +587,14 @@ class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets { List get inputs => [ ...super.inputs, const Source.pattern('{BUILD_DIR}/app.dill'), - const Source.artifact( + Source.artifact( Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: BuildMode.debug, ), - const Source.artifact( + Source.artifact( Artifact.vmSnapshotData, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: BuildMode.debug, ), ]; 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 f729baee6c4ae..3bd75b8451e91 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 @@ -46,7 +46,7 @@ class BuildHooks extends Target { final FileSystem fileSystem = environment.fileSystem; final TargetPlatform targetPlatform = platform == HookPlatform.web - ? TargetPlatform.web_javascript + ? const TargetPlatform(.web, .unknown) : _getTargetPlatformFromEnvironment(environment, name); final Uri projectUri = environment.projectDir.uri; @@ -193,7 +193,7 @@ class LinkHooks extends Target { final Uri projectUri = environment.projectDir.uri; final FileSystem fileSystem = environment.fileSystem; final TargetPlatform targetPlatform = platform == HookPlatform.web - ? TargetPlatform.web_javascript + ? const TargetPlatform(.web, .unknown) : _getTargetPlatformFromEnvironment(environment, name); final String? buildModeEnvironment = environment.defines[kBuildMode]; 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 57775b9065d9d..22eacaf4d4bdf 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -187,7 +187,10 @@ class Dart2JSTarget extends Dart2WebTarget { .getHostArtifact(HostArtifact.webPlatformKernelFolder) .path; final sharedCommandOptions = [ - artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), + artifacts.getArtifactPath( + Artifact.engineDartBinary, + platform: const TargetPlatform(.web, .unknown), + ), 'compile', 'js', '--platform-binaries=$platformBinariesPath', @@ -360,7 +363,10 @@ class Dart2WasmTarget extends Dart2WebTarget { final List dartDefines = computeDartDefines(environment); final compilationArgs = [ - artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), + artifacts.getArtifactPath( + Artifact.engineDartBinary, + platform: const TargetPlatform(.web, .unknown), + ), 'compile', 'wasm', '--packages=${findPackageConfigFileOrDefault(environment.projectDir).path}', @@ -665,7 +671,7 @@ class WebReleaseBundle extends Target { environment, environment.outputDir.childDirectory('assets'), dartHookResult: dartHookResult, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), buildMode: buildMode, ); final Depfile bundledDepfile = _bundleLocalRobotoFallback(environment, depfile); diff --git a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart index a87a36960bc76..c3510e054c6ae 100644 --- a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart +++ b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart @@ -175,16 +175,10 @@ class ShaderCompiler { bool _hasLoggedSecurityBlockError = false; List _shaderTargetsFromTargetPlatform(TargetPlatform targetPlatform) { - switch (targetPlatform) { - case TargetPlatform.android_x64: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android: - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + switch (targetPlatform.type) { + case .android: + case .linux: + case .windows: return [ '--sksl', '--runtime-stage-gles', @@ -192,20 +186,20 @@ class ShaderCompiler { '--runtime-stage-vulkan', ]; - case TargetPlatform.ios: + case .ios: return ['--runtime-stage-metal']; - case TargetPlatform.darwin: + case .macos: return ['--sksl', '--runtime-stage-metal']; - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: + case .fuchsia: + case .tester: return ['--sksl', '--runtime-stage-vulkan']; - case TargetPlatform.web_javascript: + case .web: return ['--sksl']; - case TargetPlatform.unsupported: + case .custom: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -248,7 +242,7 @@ class ShaderCompiler { impellerc.path, ...targets, '--iplr', - if (targetPlatform == TargetPlatform.web_javascript) '--json', + if (targetPlatform.type == .web) '--json', '--sl=$outputPath', '--spirv=$outputPath.spirv', '--input=${input.path}', diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 61203a9f33519..63e626c7245a2 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -31,8 +31,8 @@ var _kDefaultTargets = [ // Shared targets const CopyAssets(), const KernelSnapshot(), - const AotElfProfile(TargetPlatform.android_arm), - const AotElfRelease(TargetPlatform.android_arm), + const AotElfProfile(TargetPlatform(.android, .armv7)), + const AotElfRelease(TargetPlatform(.android, .armv7)), const AotAssemblyProfile(), const AotAssemblyRelease(), // macOS targets @@ -44,15 +44,15 @@ var _kDefaultTargets = [ const ProfileUnpackMacOS(), const ReleaseUnpackMacOS(), // Linux targets - const DebugBundleLinuxAssets(TargetPlatform.linux_x64), - const DebugBundleLinuxAssets(TargetPlatform.linux_arm64), - const DebugBundleLinuxAssets(TargetPlatform.linux_riscv64), - const ProfileBundleLinuxAssets(TargetPlatform.linux_x64), - const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64), - const ProfileBundleLinuxAssets(TargetPlatform.linux_riscv64), - const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64), - const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64), - const ReleaseBundleLinuxAssets(TargetPlatform.linux_riscv64), + const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)), + const DebugBundleLinuxAssets(TargetPlatform(.linux, .arm64)), + const DebugBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), + const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)), + const ProfileBundleLinuxAssets(TargetPlatform(.linux, .arm64)), + const ProfileBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), + const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)), + const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .arm64)), + const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), const ReleaseAndroidApplication(), // This is a one-off rule for bundle and aot compat. const CopyFlutterBundle(), @@ -81,14 +81,14 @@ var _kDefaultTargets = [ const ProfileUnpackIOS(), const ReleaseUnpackIOS(), // Windows targets - const UnpackWindows(TargetPlatform.windows_x64), - const UnpackWindows(TargetPlatform.windows_arm64), - const DebugBundleWindowsAssets(TargetPlatform.windows_x64), - const DebugBundleWindowsAssets(TargetPlatform.windows_arm64), - const ProfileBundleWindowsAssets(TargetPlatform.windows_x64), - const ProfileBundleWindowsAssets(TargetPlatform.windows_arm64), - const ReleaseBundleWindowsAssets(TargetPlatform.windows_x64), - const ReleaseBundleWindowsAssets(TargetPlatform.windows_arm64), + const UnpackWindows(TargetPlatform(.windows, .x64)), + const UnpackWindows(TargetPlatform(.windows, .arm64)), + const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)), + const DebugBundleWindowsAssets(TargetPlatform(.windows, .arm64)), + const ProfileBundleWindowsAssets(TargetPlatform(.windows, .x64)), + const ProfileBundleWindowsAssets(TargetPlatform(.windows, .arm64)), + const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .x64)), + const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .arm64)), ]; /// Assemble provides a low level API to interact with the flutter tool build diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index 32ef91fc71a45..d4df0769a7951 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -111,33 +111,27 @@ class BuildBundleCommand extends BuildSubCommand { final String targetPlatform = stringArg('target-platform')!; final platform = TargetPlatform.fromName(targetPlatform); // Check for target platforms that are only allowed via feature flags. - switch (platform) { - case TargetPlatform.darwin: + switch (platform.type) { + case .macos: if (!featureFlags.isMacOSEnabled) { throwToolExit('macOS is not a supported target platform.'); } - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .windows: if (!featureFlags.isWindowsEnabled) { throwToolExit('Windows is not a supported target platform.'); } - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: + case .linux: if (!featureFlags.isLinuxEnabled) { throwToolExit('Linux is not a supported target platform.'); } - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.ios: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: + case .android: + case .fuchsia: + case .ios: + case .tester: + case .web: + case .custom: break; - case TargetPlatform.unsupported: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index 5131df96364ac..c6faa58f1c60e 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -936,7 +936,7 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { late final Future buildableIOSApp = () async { final app = await applicationPackages?.getPackageForPlatform( - TargetPlatform.ios, + FlutterDarwinPlatform.ios.targetPlatform, buildInfo: await cachedBuildInfo, ) as BuildableIOSApp?; @@ -978,7 +978,10 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { final BuildableIOSApp app = await buildableIOSApp; final logTarget = environmentType == EnvironmentType.simulator ? 'simulator' : 'device'; - final String typeName = globals.artifacts!.getEngineType(TargetPlatform.ios, buildInfo.mode); + final String typeName = globals.artifacts!.getEngineType( + FlutterDarwinPlatform.ios.targetPlatform, + buildInfo.mode, + ); globals.printStatus(switch (xcodeBuildAction) { XcodeBuildAction.build => 'Building $app for $logTarget ($typeName)...', XcodeBuildAction.archive => 'Archiving $app...', 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 432e9519afc37..e4dcc6eb8eea4 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -744,7 +744,7 @@ end final Status status = globals.logger.startProgress(' ├─Copying Flutter.xcframework...'); final String engineCacheFlutterFrameworkDirectory = globals.artifacts!.getArtifactPath( Artifact.flutterXcframework, - platform: TargetPlatform.ios, + platform: FlutterDarwinPlatform.ios.targetPlatform, mode: buildInfo.mode, ); final String flutterFrameworkFileName = globals.fs.path.basename( @@ -798,7 +798,7 @@ end flutterRootDir: globals.fs.directory(Cache.flutterRoot), defines: { kTargetFile: targetFile, - kTargetPlatform: TargetPlatform.ios.getName(), + kTargetPlatform: FlutterDarwinPlatform.ios.targetPlatform.getName(), kIosArchs: defaultIOSArchsForEnvironment( sdkType, globals.artifacts!, diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart index fc8f602e1744c..2ba936945018b 100644 --- a/packages/flutter_tools/lib/src/commands/build_linux.dart +++ b/packages/flutter_tools/lib/src/commands/build_linux.dart @@ -72,7 +72,7 @@ class BuildLinuxCommand extends BuildSubCommand { final BuildInfo buildInfo = await getBuildInfo(); final targetPlatform = TargetPlatform.fromName(stringArg('target-platform')!); final needCrossBuild = - _operatingSystemUtils.hostPlatform.platformName != targetPlatform.simpleName; + _operatingSystemUtils.hostPlatform.platformName != targetPlatform.cpuArch.dartName; if (!featureFlags.isLinuxEnabled) { throwToolExit( @@ -88,14 +88,14 @@ class BuildLinuxCommand extends BuildSubCommand { } // TODO(fujino): https://github.com/flutter/flutter/issues/74929 if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_x64 && - targetPlatform == TargetPlatform.linux_arm64) { + targetPlatform == const TargetPlatform(.linux, .arm64)) { throwToolExit( 'Cross-build from Linux x64 host to Linux arm64 target is not currently supported.', ); } // Building for riscv64 (on a non-riscv64 host) is experimental if (_operatingSystemUtils.hostPlatform != HostPlatform.linux_riscv64 && - targetPlatform == TargetPlatform.linux_riscv64 && + targetPlatform == const TargetPlatform(.linux, .riscv64) && !featureFlags.isRiscv64SupportEnabled) { throwToolExit( 'Building for Linux riscv64 is currently an experimental feature. To enable, run "flutter config --enable-riscv64"', 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 52cf414c511df..d26ada8237620 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos_framework.dart @@ -258,7 +258,7 @@ end flutterRootDir: globals.fs.directory(Cache.flutterRoot), defines: { kTargetFile: targetFile, - kTargetPlatform: TargetPlatform.darwin.getName(), + kTargetPlatform: FlutterDarwinPlatform.macos.targetPlatform.getName(), kDarwinArchs: defaultMacOSArchsForEnvironment( globals.artifacts!, ).map((CpuArch e) => e.darwinArchName).join(' '), @@ -316,7 +316,7 @@ end final Status status = globals.logger.startProgress(' ├─Copying FlutterMacOS.xcframework...'); final String engineCacheFlutterFrameworkDirectory = globals.artifacts!.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: buildInfo.mode, ); final String flutterFrameworkFileName = globals.fs.path.basename( diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index fb29bc4b12102..f88fbe563cf94 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -468,7 +468,11 @@ class DaemonDomain extends Domain { void handlePlatformType(PlatformType platform) { final reasons = >[]; switch (platform) { - case PlatformType.linux: + case .tester: + case .unsupported: + // Not user-facing project platforms. + return; + case .linux: if (!featureFlags.isLinuxEnabled) { reasons.add({ 'reasonText': 'the Linux feature is not enabled', @@ -483,7 +487,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.macos: + case .macos: if (!featureFlags.isMacOSEnabled) { reasons.add({ 'reasonText': 'the macOS feature is not enabled', @@ -498,7 +502,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.windows: + case .windows: if (!featureFlags.isWindowsEnabled) { reasons.add({ 'reasonText': 'the Windows feature is not enabled', @@ -514,7 +518,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.ios: + case .ios: if (!featureFlags.isIOSEnabled) { reasons.add({ 'reasonText': 'the iOS feature is not enabled', @@ -529,7 +533,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.android: + case .android: if (!featureFlags.isAndroidEnabled) { reasons.add({ 'reasonText': 'the Android feature is not enabled', @@ -545,7 +549,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.web: + case .web: if (!featureFlags.isWebEnabled) { reasons.add({ 'reasonText': 'the Web feature is not enabled', @@ -560,7 +564,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.fuchsia: + case .fuchsia: if (!featureFlags.isFuchsiaEnabled) { reasons.add({ 'reasonText': 'the Fuchsia feature is not enabled', @@ -576,7 +580,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case PlatformType.custom: + case .custom: if (!featureFlags.areCustomDevicesEnabled) { reasons.add({ 'reasonText': 'the custom devices feature is not enabled', @@ -697,7 +701,7 @@ class AppDomain extends Domain { ResidentRunner runner; - if (await device.targetPlatform == TargetPlatform.web_javascript) { + if ((await device.targetPlatform).type == .web) { runner = webRunnerFactory!.createWebRunner( flutterDevice, flutterProject: flutterProject, @@ -1416,7 +1420,7 @@ Future> _deviceToMap(Device device) async { return { 'id': device.id, 'name': device.displayName, - 'platform': (await device.targetPlatform).getName(), + 'platform': (await device.targetPlatform).devicePlatformName, 'emulator': await device.isLocalEmulator, 'category': device.category?.toString(), 'platformType': device.platformType?.toString(), diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index ff276124bbcf6..d092a635bedc0 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -556,7 +556,7 @@ class RunCommand extends RunCommandBase { if (featureFlags.isWebEnabled && devices != null && devices!.length == 1 && - await devices!.single.targetPlatform == TargetPlatform.web_javascript) { + (await devices!.single.targetPlatform).type == .web) { final WebDevServerConfig webDevServerConfig = await webDevServerConfigCore(); return webDevServerConfig; } @@ -578,7 +578,7 @@ class RunCommand extends RunCommandBase { if (devices!.length > 1) { return '$command/all'; } - return '$command/${(await devices![0].targetPlatform).getName()}'; + return '$command/${(await devices![0].targetPlatform).devicePlatformName}'; } @override @@ -616,12 +616,12 @@ class RunCommand extends RunCommandBase { } else if (devices!.length == 1) { final Device device = devices![0]; final TargetPlatform platform = await device.targetPlatform; - anyAndroidDevices = platform == TargetPlatform.android; - anyIOSDevices = platform == TargetPlatform.ios; + anyAndroidDevices = platform.type == .android; + anyIOSDevices = platform.type == .ios; if (device is IOSDevice && device.isWirelesslyConnected) { anyWirelessIOSDevices = true; } - deviceType = platform.getName(); + deviceType = platform.devicePlatformName; deviceOsVersion = await device.sdkNameAndVersion; isEmulator = await device.isLocalEmulator; } else { @@ -630,8 +630,8 @@ class RunCommand extends RunCommandBase { isEmulator = false; for (final Device device in devices!) { final TargetPlatform platform = await device.targetPlatform; - anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android); - anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios); + anyAndroidDevices = anyAndroidDevices || (platform.type == .android); + anyIOSDevices = anyIOSDevices || (platform.type == .ios); if (device is IOSDevice && device.isWirelesslyConnected) { anyWirelessIOSDevices = true; } @@ -723,7 +723,7 @@ class RunCommand extends RunCommandBase { } if (userIdentifier != null && - devices!.every((Device device) => device.platformType != PlatformType.android)) { + devices!.every((Device device) => device.platformType != .android)) { throwToolExit( '--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.', ); @@ -967,7 +967,10 @@ class RunCommand extends RunCommandBase { timingLabelParts: [ if (hotMode) 'hot' else 'cold', getBuildMode().cliName, - if (devices!.length == 1) (await devices![0].targetPlatform).getName() else 'multiple', + if (devices!.length == 1) + (await devices![0].targetPlatform).devicePlatformName + else + 'multiple', if (devices!.length == 1 && await devices![0].isLocalEmulator) 'emulator' else null, ], endTimeOverride: appStartedTime, diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 78f4cd5a4c3c2..76d56f0900b20 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -640,7 +640,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { 'Ensure that `flutter doctor` shows at least one connected device', ); } - if (integrationTestDevice.platformType == PlatformType.web) { + if (integrationTestDevice.platformType == .web) { // TODO(jiahaog): Support web. https://github.com/flutter/flutter/issues/66264 throwToolExit('Web devices are not supported for integration tests yet.'); } @@ -802,7 +802,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { packageConfigPath: packageConfigPath, flavor: flavor, includeAssetsFromDevDependencies: true, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); if (build != 0) { throwToolExit('Error: Failed to build asset bundle'); @@ -811,7 +811,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { await writeBundle( globals.fs.directory(globals.fs.path.join('build', 'unit_test_assets')), assetBundle.entries, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), impellerStatus: impellerStatus, processManager: globals.processManager, fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 51d2c2797c7d4..40dbf0762e64e 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -65,9 +65,9 @@ class TargetModel { /// Infers the appropriate [TargetModel] from a given [TargetPlatform]. static TargetModel fromTargetPlatform(TargetPlatform? platform) { - return switch (platform) { - TargetPlatform.web_javascript => TargetModel.dartdevc, - TargetPlatform.fuchsia_arm64 || TargetPlatform.fuchsia_x64 => TargetModel.flutterRunner, + return switch (platform?.type) { + .web => TargetModel.dartdevc, + .fuchsia => TargetModel.flutterRunner, _ => TargetModel.flutter, }; } @@ -280,7 +280,7 @@ class KernelCompiler { String? nativeAssets, }) async { final TargetPlatform? platform = targetModel == TargetModel.dartdevc - ? TargetPlatform.web_javascript + ? const TargetPlatform(.web, .unknown) : null; // This is a URI, not a file path, so the forward slash is correct even on Windows. if (!sdkRoot.endsWith('/')) { @@ -545,7 +545,7 @@ class ResidentCompilerFactory { TargetModel targetModel = targetModelOverride ?? .flutter; // Configure the compiler to target the DDC runtime. - if (targetPlatform case .web_javascript) { + if (targetPlatform.type == .web) { sdkRoot = artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path; targetModel = .dartdevc; @@ -578,7 +578,7 @@ class ResidentCompilerFactory { ], ); } else { - if (targetPlatform case .fuchsia_arm64 || .fuchsia_x64) { + if (targetPlatform.type == .fuchsia) { targetModel = .flutterRunner; } buildInfo = buildInfo.copyWith( @@ -935,7 +935,7 @@ class DefaultResidentCompiler implements ResidentCompiler { String? nativeAssetsUri, }) async { final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) - ? TargetPlatform.web_javascript + ? const TargetPlatform(.web, .unknown) : null; late final List commandToStartFrontendServer; if (frontendServerStarterPath != null && frontendServerStarterPath!.isNotEmpty) { diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart index 55e70bf42a030..f9c177001ed44 100644 --- a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart +++ b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart @@ -776,14 +776,15 @@ class CustomDevice extends Device { } @override - Future get targetPlatform async => _config.platform ?? TargetPlatform.linux_arm64; + Future get targetPlatform async => + _config.platform ?? const TargetPlatform(.linux, .arm64); @override Future get cpuArch async { // Custom devices only support Linux target platforms (see // CustomDeviceConfig), so the arch is derived from that. - return switch (_config.platform) { - TargetPlatform.linux_x64 => CpuArch.x64, + return switch (_config.platform?.cpuArch) { + .x64 => CpuArch.x64, _ => CpuArch.arm64, }; } diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart index ad2080cf6619a..535c33850be7c 100644 --- a/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart +++ b/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart @@ -98,8 +98,8 @@ class CustomDeviceConfig { }) : assert(forwardPortCommand == null || forwardPortSuccessRegex != null), assert( platform == null || - platform == TargetPlatform.linux_x64 || - platform == TargetPlatform.linux_arm64, + platform == const TargetPlatform(.linux, .x64) || + platform == const TargetPlatform(.linux, .arm64), ); /// Create a CustomDeviceConfig from some JSON value. @@ -144,8 +144,8 @@ class CustomDeviceConfig { } if (platform != null && - platform != TargetPlatform.linux_arm64 && - platform != TargetPlatform.linux_x64) { + platform != const TargetPlatform(.linux, .arm64) && + platform != const TargetPlatform(.linux, .x64)) { throw const CustomDeviceRevivalException.fromDescriptions( _kPlatform, 'null or one of linux-arm64, linux-x64', @@ -243,7 +243,7 @@ class CustomDeviceConfig { id: 'pi', label: 'Raspberry Pi', sdkNameAndVersion: 'Raspberry Pi 4 Model B+', - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), enabled: false, pingCommand: const ['ping', '-w', '500', '-n', '1', 'raspberrypi'], pingSuccessRegex: RegExp(r'[<=]\d+ms'), diff --git a/packages/flutter_tools/lib/src/darwin/darwin.dart b/packages/flutter_tools/lib/src/darwin/darwin.dart index 87feaf935738f..3a1f10980e47b 100644 --- a/packages/flutter_tools/lib/src/darwin/darwin.dart +++ b/packages/flutter_tools/lib/src/darwin/darwin.dart @@ -18,7 +18,7 @@ import '../project.dart'; enum FlutterDarwinPlatform { ios( binaryName: 'Flutter', - targetPlatform: TargetPlatform.ios, + targetPlatform: TargetPlatform(.ios, .arm64), swiftPackagePlatform: SwiftPackagePlatform.ios, artifactName: 'ios', artifactZip: 'artifacts.zip', @@ -27,7 +27,7 @@ enum FlutterDarwinPlatform { ), macos( binaryName: 'FlutterMacOS', - targetPlatform: TargetPlatform.darwin, + targetPlatform: TargetPlatform(.macos, .x64), swiftPackagePlatform: SwiftPackagePlatform.macos, artifactName: 'darwin-x64', artifactZip: 'framework.zip', @@ -110,7 +110,10 @@ enum FlutterDarwinPlatform { /// Returns corresponding [FlutterDarwinPlatform] for the [targetPlatform]. static FlutterDarwinPlatform? fromTargetPlatform(TargetPlatform targetPlatform) { for (final FlutterDarwinPlatform darwinPlatform in FlutterDarwinPlatform.values) { - if (targetPlatform == darwinPlatform.targetPlatform) { + // Match on the platform type (family) only. A [FlutterDarwinPlatform] + // describes an OS (iOS or macOS) and is architecture-agnostic, whereas + // [TargetPlatform] equality also considers the CPU architecture. + if (targetPlatform.type == darwinPlatform.targetPlatform.type) { return darwinPlatform; } } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 13382fec0471b..423e8d601b798 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -41,23 +41,6 @@ enum Category { } } -/// The platform sub-folder that a device type supports. -enum PlatformType { - web, - android, - ios, - linux, - macos, - windows, - fuchsia, - custom; - - @override - String toString() => name; - - static PlatformType? fromString(String platformType) => values.asNameMap()[platformType]; -} - /// A discovery mechanism for flutter-supported development devices. abstract class DeviceManager { DeviceManager({required Logger logger}) : _logger = logger; @@ -356,9 +339,8 @@ class DeviceDiscoverySupportFilter { Future isDeviceSupportedForAll(Device device) async { final TargetPlatform devicePlatform = await device.targetPlatform; return await device.isSupported() && - devicePlatform != TargetPlatform.fuchsia_arm64 && - devicePlatform != TargetPlatform.fuchsia_x64 && - devicePlatform != TargetPlatform.web_javascript && + devicePlatform.type != .fuchsia && + devicePlatform.type != .web && await isDeviceSupportedForProject(device); } @@ -705,13 +687,16 @@ abstract class Device { Future supportMessage() async => await isSupported() ? 'Supported' : 'Unsupported'; /// The device's platform. - Future get targetPlatform; + /// + /// By default this is derived from [platformType] and [cpuArch]. Subclasses + /// may override this if they need custom behavior. + Future get targetPlatform async => TargetPlatform(platformType!, await cpuArch); /// The CPU architecture of the device. Future get cpuArch; /// Platform name for display only. - Future get targetPlatformDisplayName async => (await targetPlatform).getName(); + Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; Future get sdkNameAndVersion; @@ -849,7 +834,7 @@ abstract class Device { var supportIndicator = await device.isSupported() ? '' : ' (unsupported)'; final TargetPlatform targetPlatform = await device.targetPlatform; if (await device.isLocalEmulator) { - final type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator'; + final type = targetPlatform.type == .ios ? 'simulator' : 'emulator'; supportIndicator += ' ($type)'; } table.add([ @@ -885,7 +870,7 @@ abstract class Device { 'name': name, 'id': id, 'isSupported': await isSupported(), - 'targetPlatform': (await targetPlatform).getName(), + 'targetPlatform': (await targetPlatform).devicePlatformName, 'cpuArch': (await cpuArch).name, 'emulator': isLocalEmu, 'sdk': await sdkNameAndVersion, diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart index 1dbd5c9cbab57..fcfe2d6c2dfe0 100644 --- a/packages/flutter_tools/lib/src/emulator.dart +++ b/packages/flutter_tools/lib/src/emulator.dart @@ -15,6 +15,7 @@ import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/process.dart'; +import 'build_info.dart'; import 'device.dart'; import 'ios/ios_emulators.dart'; diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart index c61de499a28c0..9ff3c40687b12 100644 --- a/packages/flutter_tools/lib/src/flutter_application_package.dart +++ b/packages/flutter_tools/lib/src/flutter_application_package.dart @@ -49,11 +49,8 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { BuildInfo? buildInfo, File? applicationBinary, }) async { - switch (platform) { - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + switch (platform.type) { + case .android: if (applicationBinary == null) { return AndroidApk.fromAndroidProject( FlutterProject.current().android, @@ -74,35 +71,32 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { userMessages: _userMessages, processUtils: _processUtils, ); - case TargetPlatform.ios: + case .ios: return applicationBinary == null ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo) : IOSApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.tester: + case .tester: return FlutterTesterApp.fromCurrentDirectory(globals.fs); - case TargetPlatform.darwin: + case .macos: return applicationBinary == null ? MacOSApp.fromMacOSProject(FlutterProject.current().macos) : MacOSApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.web_javascript: + case .web: if (!FlutterProject.current().web.existsSync()) { return null; } return WebApplicationPackage(FlutterProject.current()); - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: + case .linux: return applicationBinary == null ? LinuxApp.fromLinuxProject(FlutterProject.current().linux) : LinuxApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .windows: return applicationBinary == null ? WindowsApp.fromWindowsProject(FlutterProject.current().windows) : WindowsApp.fromPrebuiltApp(applicationBinary); - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.unsupported: + case .fuchsia: + case .custom: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 48f9fa1ebdb70..89d9c2aab5586 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -1237,9 +1237,6 @@ class IOSDevice extends Device { return _coreDeviceLauncher.stopApp(deviceId: id); } - @override - Future get targetPlatform async => TargetPlatform.ios; - @override Future get sdkNameAndVersion async => 'iOS ${_sdkVersion ?? 'unknown version'}'; diff --git a/packages/flutter_tools/lib/src/ios/ios_emulators.dart b/packages/flutter_tools/lib/src/ios/ios_emulators.dart index 348312240b2fa..2800d04b9b77d 100644 --- a/packages/flutter_tools/lib/src/ios/ios_emulators.dart +++ b/packages/flutter_tools/lib/src/ios/ios_emulators.dart @@ -4,6 +4,7 @@ import '../base/common.dart'; import '../base/process.dart'; +import '../build_info.dart'; import '../device.dart'; import '../emulator.dart'; import '../globals.dart' as globals; diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 4a4fa844df7f6..c9dc71e1986fe 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -463,7 +463,7 @@ Future buildXcodeProject({ if (!hasWatchCompanion) { // ONLY_ACTIVE_ARCH specifies whether the product includes only code for // the native architecture. - final onlyActiveArch = activeArch == CpuArch.fromHostPlatform(getCurrentHostPlatform()); + final onlyActiveArch = activeArch == .fromHostPlatform(getCurrentHostPlatform()); buildCommands.add('ONLY_ACTIVE_ARCH=${onlyActiveArch ? 'YES' : 'NO'}'); buildCommands.add('ARCHS=${activeArch.darwinArchName}'); @@ -729,7 +729,7 @@ bool publicHeadersChanged({ }) { final String? basePath = artifacts?.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: FlutterDarwinPlatform.ios.targetPlatform, mode: mode, environmentType: environmentType, ); diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index f91b650e56adc..645e00c771bfd 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -622,9 +622,6 @@ class IOSSimulator extends Device { ); } - @override - Future get targetPlatform async => TargetPlatform.ios; - @override Future get sdkNameAndVersion async => simulatorCategory; diff --git a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart index 30f8d09d65a53..3ac5662b0dbf9 100644 --- a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart +++ b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart @@ -7,6 +7,7 @@ import '../base/common.dart'; import '../base/file_system.dart'; import '../build_info.dart'; import '../cache.dart'; +import '../darwin/darwin.dart'; import '../flutter_manifest.dart'; import '../globals.dart' as globals; import '../project.dart'; @@ -15,7 +16,7 @@ import 'xcodeproj.dart'; String flutterMacOSFrameworkDir(BuildMode mode, FileSystem fileSystem, Artifacts artifacts) { final String flutterMacOSFramework = artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: FlutterDarwinPlatform.macos.targetPlatform, mode: mode, ); return fileSystem.path.normalize(fileSystem.path.dirname(flutterMacOSFramework)); @@ -133,14 +134,14 @@ void _updateGeneratedEnvironmentVariablesScript({ /// Build name parsed and validated from build info and manifest. Used for CFBundleShortVersionString. String? parsedBuildName({required FlutterManifest manifest, BuildInfo? buildInfo}) { final String? buildNameToParse = buildInfo?.buildName ?? manifest.buildName; - return validatedBuildNameForPlatform(TargetPlatform.ios, buildNameToParse, globals.logger); + return validatedBuildNameForPlatform(PlatformType.ios, buildNameToParse, globals.logger); } /// Build number parsed and validated from build info and manifest. Used for CFBundleVersion. String? parsedBuildNumber({required FlutterManifest manifest, BuildInfo? buildInfo}) { String? buildNumberToParse = buildInfo?.buildNumber ?? manifest.buildNumber; final String? buildNumber = validatedBuildNumberForPlatform( - TargetPlatform.ios, + PlatformType.ios, buildNumberToParse, globals.logger, ); @@ -150,7 +151,7 @@ String? parsedBuildNumber({required FlutterManifest manifest, BuildInfo? buildIn // Drop back to parsing build name if build number is not present. Build number is optional in the manifest, but // FLUTTER_BUILD_NUMBER is required as the backing value for the required CFBundleVersion. buildNumberToParse = buildInfo?.buildName ?? manifest.buildName; - return validatedBuildNumberForPlatform(TargetPlatform.ios, buildNumberToParse, globals.logger); + return validatedBuildNumberForPlatform(PlatformType.ios, buildNumberToParse, globals.logger); } /// List of lines of build settings. Example: 'FLUTTER_BUILD_DIR=build' diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index bd8702f5905ca..581c51f509fc6 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -480,7 +480,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', @@ -494,7 +494,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', @@ -516,7 +516,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index 3e0ec38639659..654d6d6ae60b2 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -97,10 +97,7 @@ Future runFlutterSpecificHooks({ buildDataAssets: buildDataAssets, ); - final BuildMode buildMode = _getBuildMode( - environmentDefines, - targetPlatform == TargetPlatform.tester, - ); + final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); final bool linkingEnabled = _nativeAssetsLinkingEnabled(buildMode); final DartHooksResult linkResult; if (linkingEnabled) { @@ -225,10 +222,7 @@ List _getTargets({ if (featureFlags.isDartDataAssetsEnabled && buildDataAssets) SupportedAssetTypes.dataAssets, ]; - final BuildMode buildMode = _getBuildMode( - environmentDefines, - targetPlatform == TargetPlatform.tester, - ); + final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); return AssetBuildTarget.targetsFor( targetPlatform: targetPlatform, @@ -262,10 +256,7 @@ Future<({List targets, BuildMode buildMode, bool linkingEnable if (featureFlags.isDartDataAssetsEnabled && buildDataAssets) SupportedAssetTypes.dataAssets, ]; - final BuildMode buildMode = _getBuildMode( - environmentDefines, - targetPlatform == TargetPlatform.tester, - ); + final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); if (supportedAssetTypes.contains(SupportedAssetTypes.codeAssets)) { for (final CodeAssetTarget target in targets.whereType()) { @@ -485,7 +476,7 @@ Future> installCodeAssets({ required Uri targetUri, }) async { final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); - final flutterTester = targetPlatform == TargetPlatform.tester; + final flutterTester = targetPlatform.type == .tester; final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); final String? codesignIdentity = environmentDefines[kCodesignIdentity]; @@ -962,27 +953,20 @@ Never _throwNativeAssetsLinkFailed() { } OS getNativeOSFromTargetPlatform(TargetPlatform platform) { - switch (platform) { - case TargetPlatform.ios: + switch (platform.type) { + case .ios: return OS.iOS; - case TargetPlatform.darwin: + case .macos: return OS.macOS; - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: + case .linux: return OS.linux; - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .windows: return OS.windows; - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: + case .fuchsia: return OS.fuchsia; - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + case .android: return OS.android; - case TargetPlatform.tester: + case .tester: if (const LocalPlatform().isMacOS) { return OS.macOS; } else if (const LocalPlatform().isLinux) { @@ -992,9 +976,10 @@ OS getNativeOSFromTargetPlatform(TargetPlatform platform) { } else { throw StateError('Unknown operating system'); } - case TargetPlatform.web_javascript: + case .web: throw StateError('No dart builds for web yet.'); - case TargetPlatform.unsupported: + case .custom: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart index afe0ed360744e..2b3129b424792 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart @@ -67,33 +67,32 @@ sealed class AssetBuildTarget { required List supportedAssetTypes, required Directory? buildDirectory, }) { - switch (targetPlatform) { - case TargetPlatform.windows_x64: - return _windowsTarget(supportedAssetTypes, Architecture.x64); - case TargetPlatform.linux_x64: - return _linuxTarget(supportedAssetTypes, Architecture.x64, buildMode, buildDirectory); - case TargetPlatform.linux_arm64: - return _linuxTarget(supportedAssetTypes, Architecture.arm64, buildMode, buildDirectory); - case TargetPlatform.linux_riscv64: - return _linuxTarget(supportedAssetTypes, Architecture.riscv64, buildMode, buildDirectory); - case TargetPlatform.windows_arm64: - return _windowsTarget(supportedAssetTypes, Architecture.arm64); - case TargetPlatform.darwin: + switch (targetPlatform.type) { + case .windows: + return _windowsTarget( + supportedAssetTypes, + targetPlatform.cpuArch == .arm64 ? Architecture.arm64 : Architecture.x64, + ); + case .linux: + final Architecture architecture = switch (targetPlatform.cpuArch) { + .arm64 => Architecture.arm64, + .riscv64 => Architecture.riscv64, + _ => Architecture.x64, + }; + return _linuxTarget(supportedAssetTypes, architecture, buildMode, buildDirectory); + case .macos: return _macTargets(environmentDefines, supportedAssetTypes); - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + case .android: return _androidTargets(targetPlatform, environmentDefines, supportedAssetTypes); - case TargetPlatform.ios: + case .ios: return _iosTargets(environmentDefines, fileSystem, supportedAssetTypes); - case TargetPlatform.web_javascript: + case .web: return _webTarget(supportedAssetTypes); - case TargetPlatform.tester: + case .tester: return _flutterTesterTarget(supportedAssetTypes); - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.unsupported: + case .fuchsia: + case .custom: + case .unsupported: throwToolExit('No targets defined for target platform $targetPlatform.'); } } @@ -419,32 +418,16 @@ final class FlutterTesterAssetTarget extends CodeAssetTarget { } List _androidArchs(TargetPlatform targetPlatform, String? androidArchsEnvironment) { - switch (targetPlatform) { - case TargetPlatform.android_arm: - return [CpuArch.armv7]; - case TargetPlatform.android_arm64: - return [CpuArch.arm64]; - case TargetPlatform.android_x64: - return [CpuArch.x64]; - case TargetPlatform.android: - if (androidArchsEnvironment == null) { - throw MissingDefineException(kAndroidArchs, 'native_assets'); - } - return androidArchsEnvironment.split(' ').map(getCpuArchForName).toList(); - case TargetPlatform.darwin: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.ios: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.linux_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: - case TargetPlatform.unsupported: - throwToolExit('Unsupported Android target platform: $targetPlatform.'); + if (targetPlatform.type != .android) { + throwToolExit('Unsupported Android target platform: $targetPlatform.'); } + return switch (targetPlatform.cpuArch) { + .armv7 || .arm64 || .x64 => [targetPlatform.cpuArch], + CpuArch.unknown when androidArchsEnvironment != null => + androidArchsEnvironment.split(' ').map(getCpuArchForName).toList(), + .unknown => throw MissingDefineException(kAndroidArchs, 'native_assets'), + .x86 || .riscv64 => throwToolExit('Unsupported Android target platform: $targetPlatform.'), + }; } String? _emptyToNull(String? input) { diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index 73bc965e29e4e..0adf4ba0946da 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -62,7 +62,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { // Only `flutter test` uses the // `build/native_assets//native_assets.json` file which uses absolute // paths to the shared libraries. - final OS targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); + final OS targetOS = getNativeOSFromTargetPlatform(const TargetPlatform(.tester, .unknown)); final String buildDir = getBuildDirectory(); final String osName = targetOS.name; final Uri buildUri = projectUri.resolve('$buildDir/native_assets/$osName/'); @@ -74,7 +74,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), projectUri: projectUri, fileSystem: globals.fs, buildCodeAssets: const BuildCodeAssetsOptions( @@ -89,7 +89,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), projectUri: projectUri, fileSystem: globals.fs, nativeAssetsFileUri: nativeAssetsFileUri, diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 6f49a8bf4470d..f6e5879385f75 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -263,7 +263,7 @@ class ResidentWebRunner extends ResidentRunner { }) async { final ApplicationPackage? package = await ApplicationPackageFactory.instance! .getPackageForPlatform( - TargetPlatform.web_javascript, + const TargetPlatform(.web, .unknown), buildInfo: debuggingOptions.buildInfo, ); if (package == null) { @@ -292,7 +292,9 @@ class ResidentWebRunner extends ResidentRunner { ? WebExpressionCompiler(flutterDevice!.generator!, fileSystem: _fileSystem) : null; - flutterDevice!.developmentShaderCompiler.configureCompiler(TargetPlatform.web_javascript); + flutterDevice!.developmentShaderCompiler.configureCompiler( + const TargetPlatform(.web, .unknown), + ); flutterDevice!.devFS = WebDevFS( webDevServerConfig: updatedConfig, @@ -461,7 +463,7 @@ class ResidentWebRunner extends ResidentRunner { status = _logger.startProgress('Performing hot reload...', progressId: 'hot.reload'); } - final String targetPlatform = TargetPlatform.web_javascript.getName(); + final String targetPlatform = const TargetPlatform(.web, .unknown).getName(); final String sdkName = await flutterDevice!.device!.sdkNameAndVersion; // Will be null if there is no report. @@ -750,12 +752,12 @@ class ResidentWebRunner extends ResidentRunner { _logger.printTrace('Updating assets'); final int result = await assetBundle.build( flutterHookResult: await dartBuilder?.runHooks( - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), environment: environment, logger: _logger, ), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); if (result != 0) { return UpdateFSReport(); diff --git a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart index 8ba8c0f06a65a..ed794b514db8e 100644 --- a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart +++ b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart @@ -737,7 +737,7 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)}; .directory( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), ) .parent; diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index d13755bd1ac1f..af5c5f23b1bbc 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -168,10 +168,8 @@ Future _runCmake( await buildDir.create(recursive: true); final String buildFlag = sentenceCase(buildModeName); - final bool needCrossBuildOptionsForArm64 = - needCrossBuild && targetPlatform == TargetPlatform.linux_arm64; - final bool needCrossBuildOptionsForRiscv64 = - needCrossBuild && targetPlatform == TargetPlatform.linux_riscv64; + final bool needCrossBuildOptionsForArm64 = needCrossBuild && targetPlatform.cpuArch == .arm64; + final bool needCrossBuildOptionsForRiscv64 = needCrossBuild && targetPlatform.cpuArch == .riscv64; int result; if (!globals.processManager.canRun('cmake')) { throwToolExit(globals.userMessages.cmakeMissing); diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart index 13a3a73873dfd..9cec26d20e532 100644 --- a/packages/flutter_tools/lib/src/linux/linux_device.dart +++ b/packages/flutter_tools/lib/src/linux/linux_device.dart @@ -40,16 +40,6 @@ class LinuxDevice extends DesktopDevice { @override String get name => 'Linux'; - @override - late final Future targetPlatform = () async { - if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_x64) { - return TargetPlatform.linux_x64; - } else if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_riscv64) { - return TargetPlatform.linux_riscv64; - } - return TargetPlatform.linux_arm64; - }(); - @override Future get cpuArch async => CpuArch.fromHostPlatform(_operatingSystemUtils.hostPlatform); diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index dee64414d7052..8c465ba7afe55 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -42,9 +42,6 @@ class MacOSDevice extends DesktopDevice { @override bool get supportsFlavors => true; - @override - Future get targetPlatform async => TargetPlatform.darwin; - @override Future get cpuArch async => CpuArch.fromHostPlatform(_operatingSystemUtils.hostPlatform); diff --git a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart index 6255712535097..93965242ad361 100644 --- a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart @@ -35,9 +35,6 @@ class MacOSDesignedForIPadDevice extends DesktopDevice { @override String get name => 'Mac Designed for iPad'; - @override - Future get targetPlatform async => TargetPlatform.darwin; - // "Designed for iPad" apps are only supported on Apple Silicon Macs. @override Future get cpuArch async => CpuArch.arm64; diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart index 0840c95b6373a..1cd1be7c91814 100644 --- a/packages/flutter_tools/lib/src/mdns_discovery.dart +++ b/packages/flutter_tools/lib/src/mdns_discovery.dart @@ -579,8 +579,8 @@ class MDnsVmServiceDiscovery { return; } final TargetPlatform targetPlatform = await device.targetPlatform; - switch (targetPlatform) { - case TargetPlatform.ios: + switch (targetPlatform.type) { + case .ios: _analytics.send( Event.appleUsageEvent(workflow: 'ios-mdns', parameter: 'no-ipv4-link-local'), ); @@ -591,22 +591,16 @@ class MDnsVmServiceDiscovery { 'under System Preferences > Network > iPhone USB. ' 'See https://github.com/flutter/flutter/issues/46698 for details.', ); - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.darwin: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.linux_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .android: + case .macos: + case .fuchsia: + case .linux: + case .tester: + case .web: + case .windows: + case .custom: _logger.printTrace('No interface with an ipv4 link local address was found.'); - case TargetPlatform.unsupported: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/proxied_devices/devices.dart b/packages/flutter_tools/lib/src/proxied_devices/devices.dart index 65fd5e1cc69f7..b9beb1dc8f8a0 100644 --- a/packages/flutter_tools/lib/src/proxied_devices/devices.dart +++ b/packages/flutter_tools/lib/src/proxied_devices/devices.dart @@ -493,7 +493,7 @@ class ProxiedDevice extends Device { final String id = _cast( await connection.sendRequest('device.uploadApplicationPackage', { - 'targetPlatform': _targetPlatform.getName(), + 'targetPlatform': _targetPlatform.devicePlatformName, 'applicationBinary': fileName, }), ); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 12d366f9f066c..fa37c80ba4767 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -738,7 +738,7 @@ abstract class ResidentHandlers { return false; } for (final FlutterDevice? device in flutterDevices) { - if (device!.targetPlatform == TargetPlatform.web_javascript) { + if (device!.targetPlatform.type == .web) { continue; } final List views = await device.vmService!.getFlutterViews(); @@ -891,7 +891,7 @@ abstract class ResidentHandlers { } Future _takeVmServiceScreenshot(FlutterDevice device, File outputFile) async { - if (device.targetPlatform != TargetPlatform.web_javascript) { + if (device.targetPlatform.type != .web) { return false; } assert(supportsServiceProtocol); @@ -1487,7 +1487,7 @@ abstract class ResidentRunner extends ResidentHandlers { commandHelp.b.print(); } else { final bool isRunningOnWeb = flutterDevices.every((FlutterDevice? flutterDevice) { - return flutterDevice?.targetPlatform == TargetPlatform.web_javascript; + return flutterDevice?.targetPlatform.type == .web; }); if (!isRunningOnWeb) { @@ -1556,7 +1556,7 @@ abstract class ResidentRunner extends ResidentHandlers { } // 3. Perform the standard, cross-platform eviction calls. - final supportsShaderReload = device.targetPlatform != TargetPlatform.web_javascript; + final supportsShaderReload = device.targetPlatform.type != .web; for (final String assetPath in devFS.assetPathsToEvict) { // Flutter GPU shader bundles reload the compiled ShaderLibrary in place // via the `ext.ui.gpu.reinitializeShaderLibrary` extension. It is @@ -1578,7 +1578,7 @@ abstract class ResidentRunner extends ResidentHandlers { // this throws an internal RPCError (-32603) instead of a standard MethodNotFound // error, which would break the hot reload. // See https://github.com/flutter/flutter/issues/137265 - if (device.targetPlatform != TargetPlatform.web_javascript) { + if (device.targetPlatform.type != .web) { for (final String assetPath in devFS.shaderPathsToEvict) { futures.add(vmService.flutterEvictShader(assetPath, isolateId: firstUiIsolate.id!)); } @@ -1637,28 +1637,25 @@ class OperationResultExtraTiming { } Future getMissingPackageHintForPlatform(TargetPlatform platform) async { - switch (platform) { - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + switch (platform.type) { + case .android: + if (platform.cpuArch == .unknown) { + return null; + } final FlutterProject project = FlutterProject.current(); final String manifestPath = globals.fs.path.relative(project.android.appManifestFile.path); return 'Is your project missing an $manifestPath?\nConsider running "flutter create ." to create one.'; - case TargetPlatform.ios: + case .ios: return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.'; - case TargetPlatform.android: - case TargetPlatform.darwin: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: - case TargetPlatform.linux_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .macos: + case .fuchsia: + case .linux: + case .tester: + case .web: + case .windows: + case .custom: return null; - case TargetPlatform.unsupported: + case .unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -1914,14 +1911,12 @@ class TerminalHandler { final bool isRunningOnWeb = residentRunner.flutterDevices.every(( FlutterDevice? flutterDevice, ) { - return flutterDevice?.targetPlatform == TargetPlatform.web_javascript; + return flutterDevice?.targetPlatform.type == .web; }); if (residentRunner.isRunningDebug || !isRunningOnWeb) { // DevTools are only supported in debug mode for web, see https://docs.flutter.dev/testing/build-modes#profile return residentRunner.flutterDevices - .where( - (FlutterDevice? device) => device?.targetPlatform != TargetPlatform.web_javascript, - ) + .where((FlutterDevice? device) => device?.targetPlatform.type != .web) .fold( true, (bool s, FlutterDevice? device) => diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 17ca7be7a166e..d039691be05ab 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -156,7 +156,7 @@ class HotRunner extends ResidentRunner { case 1: final Device device = flutterDevices.first.device!; final TargetPlatform targetPlatform = await device.targetPlatform; - _targetPlatformName = targetPlatform.getName(); + _targetPlatformName = targetPlatform.devicePlatformName; _targetPlatforms.add(targetPlatform); _sdkName = await device.sdkNameAndVersion; _emulator = await device.isLocalEmulator; @@ -1124,9 +1124,8 @@ class HotRunner extends ResidentRunner { uiIsolateId: view.uiIsolate!.id, viewId: view.id, windows: - (device.targetPlatform == TargetPlatform.tester && globals.platform.isWindows) || - device.targetPlatform == TargetPlatform.windows_x64 || - device.targetPlatform == TargetPlatform.windows_arm64, + (device.targetPlatform.type == .tester && globals.platform.isWindows) || + device.targetPlatform.type == .windows, ), ), ); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index bbc03a2e9d864..f7223995c0619 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -2201,38 +2201,32 @@ mixin DeviceBasedDevelopmentArtifacts on FlutterCommand { // if none is supported @protected DevelopmentArtifact? artifactFromTargetPlatform(TargetPlatform targetPlatform) { - switch (targetPlatform) { - case TargetPlatform.android: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: + switch (targetPlatform.type) { + case .android: return DevelopmentArtifact.androidGenSnapshot; - case TargetPlatform.web_javascript: + case .web: return DevelopmentArtifact.web; - case TargetPlatform.ios: + case .ios: return DevelopmentArtifact.iOS; - case TargetPlatform.darwin: + case .macos: if (featureFlags.isMacOSEnabled) { return DevelopmentArtifact.macOS; } return null; - case TargetPlatform.windows_x64: - case TargetPlatform.windows_arm64: + case .windows: if (featureFlags.isWindowsEnabled) { return DevelopmentArtifact.windows; } return null; - case TargetPlatform.linux_x64: - case TargetPlatform.linux_arm64: - case TargetPlatform.linux_riscv64: + case .linux: if (featureFlags.isLinuxEnabled) { return DevelopmentArtifact.linux; } return null; - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: - case TargetPlatform.unsupported: + case .fuchsia: + case .tester: + case .custom: + case .unsupported: return null; } } diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index 5a31e32024443..efb1c97963468 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -246,7 +246,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', @@ -260,7 +260,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', @@ -274,7 +274,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'lib', 'dev_compiler', diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index af01c969b26f5..c0e8000d12064 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -554,7 +554,7 @@ class SpawnPlugin extends PlatformPlugin { final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Compile); final ResidentCompiler residentCompiler = residentCompilerFactory.create( - targetPlatform: .tester, + targetPlatform: const TargetPlatform(.tester, .unknown), artifacts: globals.artifacts!, logger: globals.logger, processManager: globals.processManager, diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart index 28cefab64e1f6..45d5a919bb2f3 100644 --- a/packages/flutter_tools/lib/src/test/test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/test_compiler.dart @@ -201,7 +201,7 @@ class TestCompiler { fileSystem: globals.fs, shutdownHooks: globals.shutdownHooks, config: globals.config, - targetPlatform: .tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); return residentCompiler; } diff --git a/packages/flutter_tools/lib/src/test/web_test_compiler.dart b/packages/flutter_tools/lib/src/test/web_test_compiler.dart index b1dace1540957..801d38529791c 100644 --- a/packages/flutter_tools/lib/src/test/web_test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/web_test_compiler.dart @@ -149,7 +149,7 @@ class WebTestCompiler { fileSystem: _fileSystem, shutdownHooks: _shutdownHooks, config: _config, - targetPlatform: .web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); final CompilerOutput? output = await residentCompiler.recompile( @@ -204,7 +204,7 @@ class WebTestCompiler { final compilationArgs = [ _artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), 'compile', 'wasm', diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart index f87c6573369a6..306d548f532cd 100644 --- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart +++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart @@ -94,7 +94,7 @@ class FlutterTesterDevice extends Device { bool get supportsFlavors => true; @override - Future get targetPlatform async => TargetPlatform.tester; + Future get targetPlatform async => const TargetPlatform(.tester, .unknown); @override Future get cpuArch async => CpuArch.unknown; @@ -152,7 +152,7 @@ class FlutterTesterDevice extends Device { buildInfo: buildInfo, mainPath: mainPath, applicationKernelFilePath: applicationKernelFilePath, - platform: TargetPlatform.tester, + platform: const TargetPlatform(.tester, .unknown), assetDirPath: assetDirectory.path, ); diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index d6ea3be1b9ce8..a99a63198e4ae 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -167,9 +167,6 @@ abstract class ChromiumDevice extends WebDevice { return true; } - @override - Future get targetPlatform async => TargetPlatform.web_javascript; - @override Future uninstallApp(ApplicationPackage app, {String? userIdentifier}) async => true; @@ -469,9 +466,6 @@ class WebServerDevice extends WebDevice { return true; } - @override - Future get targetPlatform async => TargetPlatform.web_javascript; - @override Future uninstallApp(ApplicationPackage app, {String? userIdentifier}) async { return true; diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 70ae440fc1b51..62183da7c6833 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -170,10 +170,12 @@ Future buildWindows( } String getCmakeWindowsArch(TargetPlatform targetPlatform) { - return switch (targetPlatform) { - TargetPlatform.windows_x64 => 'x64', - TargetPlatform.windows_arm64 => 'ARM64', - _ => throw Exception('Unsupported target platform "$targetPlatform".'), + if (targetPlatform.type != .windows) { + throw Exception('Unsupported target platform "$targetPlatform".'); + } + return switch (targetPlatform.cpuArch) { + .arm64 => 'ARM64', + _ => 'x64', }; } diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart index e3014cf339161..3acc69df82476 100644 --- a/packages/flutter_tools/lib/src/windows/windows_device.dart +++ b/packages/flutter_tools/lib/src/windows/windows_device.dart @@ -38,12 +38,9 @@ class WindowsDevice extends DesktopDevice { @override String get name => 'Windows'; - @override - Future get targetPlatform async => _targetPlatform; - TargetPlatform get _targetPlatform => switch (_operatingSystemUtils.hostPlatform) { - HostPlatform.windows_arm64 => TargetPlatform.windows_arm64, - _ => TargetPlatform.windows_x64, + HostPlatform.windows_arm64 => const TargetPlatform(.windows, .arm64), + _ => const TargetPlatform(.windows, .x64), }; @override diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 1cc3967726777..fcb79e5ebee3a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -1882,7 +1882,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get targetPlatformDisplayName async => 'android'; @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); @override DeviceConnectionInterface get connectionInterface => DeviceConnectionInterface.attached; @@ -1996,7 +1996,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { String get displayName => name; @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); @override final PlatformType platformType = PlatformType.ios; @@ -2075,7 +2075,7 @@ class FakeIOSSimulator extends Fake implements IOSSimulator { bool get ephemeral => true; @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); @override final PlatformType platformType = PlatformType.ios; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart index 84ae4fca7785c..a927136422831 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart @@ -442,7 +442,7 @@ void main() { // Mock engine artifacts. _TestArtifacts uses a string like this for getArtifactPath. memoryFileSystem - .directory('Artifact.flutterXcframework.TargetPlatform.ios.debug') + .directory('Artifact.flutterXcframework.ios.debug') .createSync(recursive: true); final Directory buildDir = @@ -528,7 +528,7 @@ void main() { // Mock engine artifacts memoryFileSystem - .directory('Artifact.flutterXcframework.TargetPlatform.ios.debug') + .directory('Artifact.flutterXcframework.ios.debug') .createSync(recursive: true); final Directory buildDir = diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart index a2b47feabd5a7..aab69e37d7706 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart @@ -740,7 +740,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin', + 'TargetPlatform': 'darwin-x64', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'debug', 'DartObfuscation': 'false', @@ -848,7 +848,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin', + 'TargetPlatform': 'darwin-x64', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'release', 'DartObfuscation': 'false', @@ -974,7 +974,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin', + 'TargetPlatform': 'darwin-x64', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'release', 'DartObfuscation': 'false', @@ -1085,7 +1085,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1107,7 +1107,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1219,7 +1219,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1241,7 +1241,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -2917,7 +2917,7 @@ public func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -2939,7 +2939,7 @@ public func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios', + 'TargetPlatform': 'ios-arm64', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index 11c574670713a..18aeb31f75471 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -79,7 +79,7 @@ void main() { FakeCommand cmakeGenerationCommand({ void Function(List command)? onRun, String generator = _defaultGenerator, - TargetPlatform targetPlatform = TargetPlatform.windows_x64, + TargetPlatform targetPlatform = const TargetPlatform(.windows, .x64), }) { return FakeCommand( command: [ diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index 541d2272e8ab3..0ce272cc1e260 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -577,7 +577,10 @@ void main() { ); expect(applicationPackageIdResponse.data['id'], 0); expect(applicationPackageFactory.applicationBinaryRequested!.basename, 'test_file'); - expect(applicationPackageFactory.platformRequested, TargetPlatform.android); + expect( + applicationPackageFactory.platformRequested, + const TargetPlatform(.android, .unknown), + ); final applicationPackageId = applicationPackageIdResponse.data['result'] as String?; // Try starting the app. @@ -1162,7 +1165,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get emulatorId async => 'device'; @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); @override Future get cpuArch async => CpuArch.armv7; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart index c2a4f07f0448a..4da698dffec12 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart @@ -114,176 +114,148 @@ void main() { }); group('doctor with fake validators', () { - testUsingContext( - 'validate non-verbose output format for run without issues', - () async { - expect(await FakeQuietDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '• No issues found!\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output format for run without issues', () async { + expect(await FakeQuietDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '• No issues found!\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate non-verbose output format for run with crash', - () async { - expect(await FakeCrashingDoctor(logger).diagnose(verbose: false), isFalse); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[☠] Crashing validator (the doctor check crashed)\n' - ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' - 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' - ' ✗ Bad state: fatal error\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output format for run with crash', () async { + expect(await FakeCrashingDoctor(logger).diagnose(verbose: false), isFalse); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[☠] Crashing validator (the doctor check crashed)\n' + ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' + 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' + ' ✗ Bad state: fatal error\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); testUsingContext('validate verbose output format contains trace for run with crash', () async { expect(await FakeCrashingDoctor(logger).diagnose(), isFalse); expect(logger.statusText, contains('#0 CrashingValidator.validate')); }); - testUsingContext( - 'validate tool exit when exceeding timeout', - () async { - FakeAsync().run((FakeAsync time) { - final Doctor doctor = FakeAsyncStuckDoctor(logger); - doctor.diagnose(verbose: false); - time.elapse(const Duration(minutes: 5)); - time.flushMicrotasks(); - }); + testUsingContext('validate tool exit when exceeding timeout', () async { + FakeAsync().run((FakeAsync time) { + final Doctor doctor = FakeAsyncStuckDoctor(logger); + doctor.diagnose(verbose: false); + time.elapse(const Duration(minutes: 5)); + time.flushMicrotasks(); + }); - expect( - logger.statusText, - contains('Stuck validator that never completes exceeded maximum allowed duration of '), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + expect( + logger.statusText, + contains('Stuck validator that never completes exceeded maximum allowed duration of '), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate non-verbose output format for run with an async crash', - () async { - final completer = Completer(); - await FakeAsync().run((FakeAsync time) { - unawaited( - FakeAsyncCrashingDoctor(time, logger).diagnose(verbose: false).then((bool r) { - expect(r, isFalse); - completer.complete(); - }), - ); - time.elapse(const Duration(seconds: 1)); - time.flushMicrotasks(); - return completer.future; - }); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[☠] Async crashing validator (the doctor check crashed)\n' - ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' - 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' - ' ✗ Bad state: fatal error\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), + testUsingContext('validate non-verbose output format for run with an async crash', () async { + final completer = Completer(); + await FakeAsync().run((FakeAsync time) { + unawaited( + FakeAsyncCrashingDoctor(time, logger).diagnose(verbose: false).then((bool r) { + expect(r, isFalse); + completer.complete(); + }), ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + time.elapse(const Duration(seconds: 1)); + time.flushMicrotasks(); + return completer.future; + }); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[☠] Async crashing validator (the doctor check crashed)\n' + ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' + 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' + ' ✗ Bad state: fatal error\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate non-verbose output format when only one category fails', - () async { - expect(await FakeSinglePassingDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output format when only one category fails', () async { + expect(await FakeSinglePassingDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate non-verbose output format for a passing run', - () async { - expect(await FakePassingDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with Errors\n' - ' ✗ An error message indicating partial installation\n' - ' ! Maybe a hint will help the user\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '\n' - '! Doctor found issues in 2 categories.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output format for a passing run', () async { + expect(await FakePassingDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with Errors\n' + ' ✗ An error message indicating partial installation\n' + ' ! Maybe a hint will help the user\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '\n' + '! Doctor found issues in 2 categories.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate non-verbose output format', - () async { - expect(await FakeDoctor(logger).diagnose(verbose: false), isFalse); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✗] Missing Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Not Available Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with Errors\n' - ' ✗ An error message indicating partial installation\n' - ' ! Maybe a hint will help the user\n' - '\n' - '! Doctor found issues in 4 categories.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output format', () async { + expect(await FakeDoctor(logger).diagnose(verbose: false), isFalse); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✗] Missing Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Not Available Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with Errors\n' + ' ✗ An error message indicating partial installation\n' + ' ! Maybe a hint will help the user\n' + '\n' + '! Doctor found issues in 4 categories.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); testUsingContext('validate verbose output format', () async { expect(await FakeDoctor(logger).diagnose(), isFalse); @@ -405,45 +377,41 @@ void main() { }); }); - testUsingContext( - 'validate non-verbose output wrapping', - () async { - final wrapLogger = BufferLogger.test( - outputPreferences: OutputPreferences(wrapText: true, wrapColumn: 30), - ); - expect(await FakeDoctor(wrapLogger).diagnose(verbose: false), isFalse); - expect( - wrapLogger.statusText, - equals( - 'Doctor summary (to see all\n' - 'details, run flutter doctor\n' - '-v):\n' - '[✓] Passing Validator (with\n' - ' statusInfo)\n' - '[✗] Missing Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Not Available Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Partial Validator with\n' - ' only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with\n' - ' Errors\n' - ' ✗ An error message\n' - ' indicating partial\n' - ' installation\n' - ' ! Maybe a hint will help\n' - ' the user\n' - '\n' - '! Doctor found issues in 4\n' - ' categories.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate non-verbose output wrapping', () async { + final wrapLogger = BufferLogger.test( + outputPreferences: OutputPreferences(wrapText: true, wrapColumn: 30), + ); + expect(await FakeDoctor(wrapLogger).diagnose(verbose: false), isFalse); + expect( + wrapLogger.statusText, + equals( + 'Doctor summary (to see all\n' + 'details, run flutter doctor\n' + '-v):\n' + '[✓] Passing Validator (with\n' + ' statusInfo)\n' + '[✗] Missing Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Not Available Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Partial Validator with\n' + ' only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with\n' + ' Errors\n' + ' ✗ An error message\n' + ' indicating partial\n' + ' installation\n' + ' ! Maybe a hint will help\n' + ' the user\n' + '\n' + '! Doctor found issues in 4\n' + ' categories.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); testUsingContext('validate verbose output wrapping', () async { final wrapLogger = BufferLogger.test( @@ -494,46 +462,38 @@ void main() { }, overrides: {AnsiTerminal: () => FakeTerminal()}); group('doctor with grouped validators', () { - testUsingContext( - 'validate diagnose combines validator output', - () async { - expect(await FakeGroupedDoctor(logger).diagnose(), isTrue); - expect( - logger.statusText, - equals( - '[✓] Category 1 [0ms]\n' - ' • A helpful message\n' - ' • A helpful message\n' - '\n' - '[!] Category 2 [0ms]\n' - ' • A helpful message\n' - ' ✗ A useful error message\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate diagnose combines validator output', () async { + expect(await FakeGroupedDoctor(logger).diagnose(), isTrue); + expect( + logger.statusText, + equals( + '[✓] Category 1 [0ms]\n' + ' • A helpful message\n' + ' • A helpful message\n' + '\n' + '[!] Category 2 [0ms]\n' + ' • A helpful message\n' + ' ✗ A useful error message\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate merging assigns statusInfo and title', - () async { - // There are two subvalidators. Only the second contains statusInfo. - expect(await FakeGroupedDoctorWithStatus(logger).diagnose(), isTrue); - expect( - logger.statusText, - equals( - '[✓] First validator title (A status message) [0ms]\n' - ' • A helpful message\n' - ' • A different message\n' - '\n' - '• No issues found!\n', - ), - ); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate merging assigns statusInfo and title', () async { + // There are two subvalidators. Only the second contains statusInfo. + expect(await FakeGroupedDoctorWithStatus(logger).diagnose(), isTrue); + expect( + logger.statusText, + equals( + '[✓] First validator title (A status message) [0ms]\n' + ' • A helpful message\n' + ' • A different message\n' + '\n' + '• No issues found!\n', + ), + ); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); }); group('grouped validator merging results', () { @@ -541,86 +501,50 @@ void main() { final partial = PartialGroupedValidator('Category'); final missing = MissingGroupedValidator('Category'); - testUsingContext( - 'validate installed + installed = installed', - () async { - expect(await FakeSmallGroupDoctor(logger, installed, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[✓]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate installed + installed = installed', () async { + expect(await FakeSmallGroupDoctor(logger, installed, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[✓]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate installed + partial = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, installed, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate installed + partial = partial', () async { + expect(await FakeSmallGroupDoctor(logger, installed, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate installed + missing = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, installed, missing).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate installed + missing = partial', () async { + expect(await FakeSmallGroupDoctor(logger, installed, missing).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate partial + installed = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, partial, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate partial + installed = partial', () async { + expect(await FakeSmallGroupDoctor(logger, partial, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate partial + partial = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, partial, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate partial + partial = partial', () async { + expect(await FakeSmallGroupDoctor(logger, partial, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate partial + missing = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, partial, missing).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate partial + missing = partial', () async { + expect(await FakeSmallGroupDoctor(logger, partial, missing).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate missing + installed = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, missing, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate missing + installed = partial', () async { + expect(await FakeSmallGroupDoctor(logger, missing, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate missing + partial = partial', - () async { - expect(await FakeSmallGroupDoctor(logger, missing, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate missing + partial = partial', () async { + expect(await FakeSmallGroupDoctor(logger, missing, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); - testUsingContext( - 'validate missing + missing = missing', - () async { - expect(await FakeSmallGroupDoctor(logger, missing, missing).diagnose(), isFalse); - expect(logger.statusText, startsWith('[✗]')); - }, - overrides: {AnsiTerminal: () => FakeTerminal()}, - ); + testUsingContext('validate missing + missing = missing', () async { + expect(await FakeSmallGroupDoctor(logger, missing, missing).diagnose(), isFalse); + expect(logger.statusText, startsWith('[✗]')); + }, overrides: {AnsiTerminal: () => FakeTerminal()}); }); testUsingContext( @@ -638,17 +562,13 @@ void main() { }, ); - testUsingContext( - 'CustomDevicesWorkflow is a part of validator workflows if enabled', - () async { - final List workflows = DoctorValidatorsProvider.test( - featureFlags: TestFeatureFlags(areCustomDevicesEnabled: true), - platform: FakePlatform(), - ).workflows; - expect(workflows, contains(isA())); - }, - overrides: {FileSystem: () => fs, ProcessManager: () => fakeProcessManager}, - ); + testUsingContext('CustomDevicesWorkflow is a part of validator workflows if enabled', () async { + final List workflows = DoctorValidatorsProvider.test( + featureFlags: TestFeatureFlags(areCustomDevicesEnabled: true), + platform: FakePlatform(), + ).workflows; + expect(workflows, contains(isA())); + }, overrides: {FileSystem: () => fs, ProcessManager: () => fakeProcessManager}); group('FlutterValidator', () { late FakeFlutterVersion initialVersion; @@ -714,14 +634,10 @@ void main() { ); }); - testUsingContext( - 'ensure fake is being used and initialized', - () { - expect(fakeAnalytics.sentEvents.length, 0); - expect(fakeAnalytics.okToSend, true); - }, - overrides: {Analytics: () => fakeAnalytics}, - ); + testUsingContext('ensure fake is being used and initialized', () { + expect(fakeAnalytics.sentEvents.length, 0); + expect(fakeAnalytics.okToSend, true); + }, overrides: {Analytics: () => fakeAnalytics}); testUsingContext( 'contains installed', @@ -879,26 +795,22 @@ void main() { }, ); - testUsingContext( - 'grouped validator subresult and subvalidators different lengths', - () async { - final fakeDoctor = FakeGroupedDoctorWithCrash(logger, clock: fakeSystemClock); - await fakeDoctor.diagnose(verbose: false); - - expect(fakeDoctor.validators, hasLength(1)); - expect(fakeDoctor.validators.first.runtimeType == FakeGroupedValidatorWithCrash, true); - expect(fakeAnalytics.sentEvents, hasLength(0)); - - // Attempt to send a random event to ensure that the - // analytics package is still working, despite not sending - // above (as expected) - final testEvent = Event.analyticsCollectionEnabled(status: true); - fakeAnalytics.send(testEvent); - expect(fakeAnalytics.sentEvents, hasLength(1)); - expect(fakeAnalytics.sentEvents, contains(testEvent)); - }, - overrides: {Analytics: () => fakeAnalytics}, - ); + testUsingContext('grouped validator subresult and subvalidators different lengths', () async { + final fakeDoctor = FakeGroupedDoctorWithCrash(logger, clock: fakeSystemClock); + await fakeDoctor.diagnose(verbose: false); + + expect(fakeDoctor.validators, hasLength(1)); + expect(fakeDoctor.validators.first.runtimeType == FakeGroupedValidatorWithCrash, true); + expect(fakeAnalytics.sentEvents, hasLength(0)); + + // Attempt to send a random event to ensure that the + // analytics package is still working, despite not sending + // above (as expected) + final testEvent = Event.analyticsCollectionEnabled(status: true); + fakeAnalytics.send(testEvent); + expect(fakeAnalytics.sentEvents, hasLength(1)); + expect(fakeAnalytics.sentEvents, contains(testEvent)); + }, overrides: {Analytics: () => fakeAnalytics}); testUsingContext('sending events can be skipped', () async { await FakePassingDoctor(logger).diagnose(verbose: false, sendEvent: false); @@ -1326,7 +1238,8 @@ class FakeDevice extends Fake implements Device { Future get sdkNameAndVersion async => '1.2.3'; @override - Future get targetPlatform => Future.value(TargetPlatform.android); + Future get targetPlatform => + Future.value(const TargetPlatform(.android, .unknown)); } class FakeTerminal extends Fake implements AnsiTerminal { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart index 2271447a6d644..fb15a0167c6cc 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -935,7 +935,7 @@ class ScreenshotDevice extends Fake implements Device { final id = 'fake_device'; @override - Future get targetPlatform async => TargetPlatform.android; + Future get targetPlatform async => const TargetPlatform(.android, .unknown); @override bool supportsScreenshot = true; @@ -1130,7 +1130,7 @@ class FakeIosDevice extends Fake implements IOSDevice { bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); } class FakeChromiumDriveDevice extends Fake implements ChromiumDevice { @@ -1162,7 +1162,7 @@ class FakeChromiumDriveDevice extends Fake implements ChromiumDevice { Future get sdkNameAndVersion async => 'Google Chrome 0.0'; @override - Future get targetPlatform async => TargetPlatform.web_javascript; + Future get targetPlatform async => const TargetPlatform(.web, .unknown); @override DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart index f953604c00af4..3244e0b06f574 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart @@ -180,7 +180,7 @@ class FakeAndroidApk extends Fake implements AndroidApk {} class FakeIOSDevice extends Fake implements IOSDevice { @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); @override Future isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false; @@ -194,7 +194,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { class FakeAndroidDevice extends Fake implements AndroidDevice { @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); @override Future isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart index e9b100fb05c26..1ad4ee46c751a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart @@ -169,7 +169,7 @@ void main() { applicationPackageFactory.applicationBinaryRequested!.readAsStringSync(), 'dummy content', ); - expect(applicationPackageFactory.platformRequested, TargetPlatform.android_arm); + expect(applicationPackageFactory.platformRequested, const TargetPlatform(.android, .armv7)); expect(fakeDevice.startAppPackage, applicationPackage); @@ -254,7 +254,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get emulatorId async => 'device'; @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); @override Future get cpuArch async => CpuArch.arm64; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index e096756bb5679..c9717c0639b02 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -286,7 +286,7 @@ void main() { () async { final command = RunCommand(); final mockDevice = FakeDevice( - targetPlatform: TargetPlatform.android_arm, + targetPlatform: const TargetPlatform(.android, .armv7), isLocalEmulator: true, sdkNameAndVersion: 'api-14', isSupported: false, @@ -545,7 +545,7 @@ void main() { final command = RunCommand(); final device = FakeDevice( platformType: PlatformType.web, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); testDeviceManager.devices = [device]; @@ -691,7 +691,7 @@ void main() { 'should only request artifacts corresponding to connected devices', () async { testDeviceManager.devices = [ - FakeDevice(targetPlatform: TargetPlatform.android_arm), + FakeDevice(targetPlatform: const TargetPlatform(.android, .armv7)), ]; expect( @@ -714,7 +714,7 @@ void main() { testDeviceManager.devices = [ FakeDevice(), - FakeDevice(targetPlatform: TargetPlatform.android_arm), + FakeDevice(targetPlatform: const TargetPlatform(.android, .armv7)), ]; expect( @@ -727,7 +727,7 @@ void main() { ); testDeviceManager.devices = [ - FakeDevice(targetPlatform: TargetPlatform.web_javascript), + FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)), ]; expect( @@ -752,7 +752,7 @@ void main() { () async { final devices = [ FakeDevice( - targetPlatform: TargetPlatform.android_arm, + targetPlatform: const TargetPlatform(.android, .armv7), platformType: PlatformType.android, ), ]; @@ -986,7 +986,7 @@ void main() { final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); testDeviceManager.devices = [device]; }); @@ -1160,7 +1160,7 @@ void main() { final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); testDeviceManager.devices = [device]; }); @@ -1425,7 +1425,7 @@ server: final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); testDeviceManager.devices = [device]; }); @@ -1931,7 +1931,7 @@ class TestDeviceManager extends DeviceManager { class FakeDevice extends Fake implements Device { FakeDevice({ bool isLocalEmulator = false, - TargetPlatform targetPlatform = TargetPlatform.ios, + TargetPlatform targetPlatform = const TargetPlatform(.ios, .arm64), String sdkNameAndVersion = '', PlatformType platformType = PlatformType.ios, bool isSupported = true, @@ -1999,7 +1999,7 @@ class FakeDevice extends Fake implements Device { Future get sdkNameAndVersion => Future.value(_sdkNameAndVersion); @override - Future get targetPlatformDisplayName async => (await targetPlatform).getName(); + Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; @override DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { @@ -2086,7 +2086,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); } class TestRunCommandForUsageValues extends RunCommand { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart index 76a40ddf8dfc0..d8d48d8e263ec 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart @@ -147,53 +147,45 @@ void main() { testDeviceManager = _TestDeviceManager(logger: BufferLogger.test()); }); - testUsingContext( - 'should not throw for a single device', - () async { - final command = ScreenshotCommand(fs: MemoryFileSystem.test()); - - final deviceUnsupportedForProject = _ScreenshotDevice( - id: '123', - name: 'Device 1', - isSupportedForProject: false, - ); - - testDeviceManager.devices = [deviceUnsupportedForProject]; - - await createTestCommandRunner(command).run(['screenshot']); - }, - overrides: {DeviceManager: () => testDeviceManager}, - ); - - testUsingContext( - 'should tool exit for multiple devices', - () async { - final command = ScreenshotCommand(fs: MemoryFileSystem.test()); - - final devicesUnsupportedForProject = <_ScreenshotDevice>[ - _ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false), - _ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false), - ]; - - testDeviceManager.devices = devicesUnsupportedForProject; - - await expectLater( - () => createTestCommandRunner(command).run(['screenshot']), - throwsToolExit(message: 'Must have a connected device for screenshot type device'), - ); - - expect( - testLogger.statusText, - contains(''' + testUsingContext('should not throw for a single device', () async { + final command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final deviceUnsupportedForProject = _ScreenshotDevice( + id: '123', + name: 'Device 1', + isSupportedForProject: false, + ); + + testDeviceManager.devices = [deviceUnsupportedForProject]; + + await createTestCommandRunner(command).run(['screenshot']); + }, overrides: {DeviceManager: () => testDeviceManager}); + + testUsingContext('should tool exit for multiple devices', () async { + final command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final devicesUnsupportedForProject = <_ScreenshotDevice>[ + _ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false), + _ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false), + ]; + + testDeviceManager.devices = devicesUnsupportedForProject; + + await expectLater( + () => createTestCommandRunner(command).run(['screenshot']), + throwsToolExit(message: 'Must have a connected device for screenshot type device'), + ); + + expect( + testLogger.statusText, + contains(''' More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. Device 1 (mobile) • 123 • android • 1.2.3 Device 2 (mobile) • 456 • android • 1.2.3 '''), - ); - }, - overrides: {DeviceManager: () => testDeviceManager}, - ); + ); + }, overrides: {DeviceManager: () => testDeviceManager}); }); } @@ -242,7 +234,8 @@ class _ScreenshotDevice extends Fake implements Device { Future get sdkNameAndVersion async => '1.2.3'; @override - Future get targetPlatform => Future.value(TargetPlatform.android); + Future get targetPlatform => + Future.value(const TargetPlatform(.android, .unknown)); @override Future get isLocalEmulator async => false; diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart index 90b23c385699e..adc20ec9e8099 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart @@ -110,7 +110,7 @@ class FakeAnalysisServer extends Fake implements AnalysisServer { class FakeGoogleChromeDevice extends Fake implements GoogleChromeDevice { @override - Future get targetPlatform async => TargetPlatform.web_javascript; + Future get targetPlatform async => const TargetPlatform(.web, .unknown); @override PlatformType? get platformType => PlatformType.web; @@ -121,7 +121,7 @@ class FakeGoogleChromeDevice extends Fake implements GoogleChromeDevice { class FakeMicrosoftEdgeDevice extends Fake implements MicrosoftEdgeDevice { @override - Future get targetPlatform async => TargetPlatform.web_javascript; + Future get targetPlatform async => const TargetPlatform(.web, .unknown); @override PlatformType? get platformType => PlatformType.web; @@ -132,7 +132,7 @@ class FakeMicrosoftEdgeDevice extends Fake implements MicrosoftEdgeDevice { class FakeCustomBrowserDevice extends Fake implements ChromiumDevice { @override - Future get targetPlatform async => TargetPlatform.web_javascript; + Future get targetPlatform async => const TargetPlatform(.web, .unknown); @override PlatformType? get platformType => PlatformType.web; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index 460cd8e772b98..fe914914bb75d 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -49,9 +49,9 @@ void main() { }); for (final targetPlatform in [ - TargetPlatform.android_arm, - TargetPlatform.android_arm64, - TargetPlatform.android_x64, + const TargetPlatform(.android, .armv7), + const TargetPlatform(.android, .arm64), + const TargetPlatform(.android, .x64), ]) { testWithoutContext('AndroidDevice.startApp allows release builds on $targetPlatform', () async { final String arch = getCpuArchForName(targetPlatform.getName()).androidArchName; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_test.dart index 160786caf8566..eff4521548b90 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_test.dart @@ -77,11 +77,11 @@ void main() { 'abi and abiList', () async { // The format is [ABI, ABI list]: expected target platform. final values = , TargetPlatform>{ - ['x86_64', 'unknown']: TargetPlatform.android_x64, - ['armeabi-v7a', 'unknown']: TargetPlatform.android_arm, - ['arm64-v8a', 'arm64-v8a,']: TargetPlatform.android_arm64, + ['x86_64', 'unknown']: const TargetPlatform(.android, .x64), + ['armeabi-v7a', 'unknown']: const TargetPlatform(.android, .armv7), + ['arm64-v8a', 'arm64-v8a,']: const TargetPlatform(.android, .arm64), // The Kindle Fire runs 32 bit apps on 64 bit hardware. - ['arm64-v8a', 'arm']: TargetPlatform.android_arm, + ['arm64-v8a', 'arm']: const TargetPlatform(.android, .armv7), }; for (final MapEntry, TargetPlatform> entry in values.entries) { diff --git a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart index e754dcbd40017..9b3948004ee74 100644 --- a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart @@ -7,8 +7,8 @@ import 'dart:async'; import 'package:flutter_tools/src/android/android_emulator.dart'; import 'package:flutter_tools/src/android/android_sdk.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/device.dart'; import 'package:test/fake.dart'; diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart index d34273ab401bc..7237025027599 100644 --- a/packages/flutter_tools/test/general.shard/application_package_test.dart +++ b/packages/flutter_tools/test/general.shard/application_package_test.dart @@ -88,7 +88,7 @@ void main() { ); await ApplicationPackageFactory.instance!.getPackageForPlatform( - TargetPlatform.android_arm, + const TargetPlatform(.android, .armv7), applicationBinary: apkFile, ); final logger = BufferLogger.test(); @@ -158,7 +158,7 @@ void main() { ); await ApplicationPackageFactory.instance!.getPackageForPlatform( - TargetPlatform.android_arm, + const TargetPlatform(.android, .armv7), applicationBinary: apkFile, ); final logger = BufferLogger.test(); @@ -214,7 +214,10 @@ void main() { ); final ApplicationPackage applicationPackage = (await ApplicationPackageFactory.instance! - .getPackageForPlatform(TargetPlatform.android_arm, applicationBinary: apkFile))!; + .getPackageForPlatform( + const TargetPlatform(.android, .armv7), + applicationBinary: apkFile, + ))!; expect(applicationPackage.name, 'app-debug.apk'); expect(applicationPackage, isA()); expect( @@ -242,7 +245,7 @@ void main() { gradleWrapperDir.childFile('gradlew.bat').writeAsStringSync('irrelevant'); await ApplicationPackageFactory.instance!.getPackageForPlatform( - TargetPlatform.android_arm, + const TargetPlatform(.android, .armv7), applicationBinary: globals.fs.file('app-debug.apk'), ); expect(fakeProcessManager, hasNoRemainingExpectations); @@ -254,7 +257,9 @@ void main() { final AndroidSdkVersion sdkVersion = FakeAndroidSdkVersion(); sdk.latestVersion = sdkVersion; - await ApplicationPackageFactory.instance!.getPackageForPlatform(TargetPlatform.android_arm); + await ApplicationPackageFactory.instance!.getPackageForPlatform( + const TargetPlatform(.android, .armv7), + ); expect(fakeProcessManager, hasNoRemainingExpectations); }, overrides: overrides, diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index 46cacd14758d2..400539586a5af 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -44,7 +44,7 @@ void main() { testWithoutContext('getArtifactPath', () { final String xcframeworkPath = artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ); expect( @@ -62,7 +62,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -72,7 +72,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -93,7 +93,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -101,7 +101,7 @@ void main() { ); final String actualReleaseFrameworkArtifact = artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.physical, ); @@ -114,7 +114,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ), fileSystem.path.join( @@ -140,7 +140,10 @@ void main() { ), ); expect( - artifacts.getArtifactPath(Artifact.flutterTester, platform: TargetPlatform.linux_arm64), + artifacts.getArtifactPath( + Artifact.flutterTester, + platform: const TargetPlatform(.linux, .arm64), + ), fileSystem.path.join( 'root', 'bin', @@ -189,7 +192,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), xcframeworkPath, @@ -201,7 +204,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), throwsToolExit(message: 'No xcframework found at $xcframeworkPath.'), @@ -214,7 +217,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), throwsToolExit(message: 'No macOS frameworks found in $xcframeworkPath'), @@ -227,7 +230,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), fileSystem.path.join(xcframeworkPath, 'macos-arm64_x86_64', 'FlutterMacOS.framework'), @@ -263,9 +266,36 @@ void main() { ); testWithoutContext('getEngineType', () { - expect(artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), 'android-arm'); - expect(artifacts.getEngineType(TargetPlatform.ios, BuildMode.release), 'ios-release'); - expect(artifacts.getEngineType(TargetPlatform.darwin), 'darwin-x64'); + expect( + artifacts.getEngineType(const TargetPlatform(.android, .armv7), BuildMode.debug), + 'android-arm', + ); + expect( + artifacts.getEngineType(const TargetPlatform(.ios, .arm64), BuildMode.release), + 'ios-release', + ); + expect(artifacts.getEngineType(const TargetPlatform(.macos, .x64)), 'darwin-x64'); + }); + + testWithoutContext('getArtifactPath resolves a generic Android target to arm64 for ' + 'architecture-independent artifacts', () { + // The Dart kernel / patched SDK is architecture independent, so the + // build system requests it with a generic Android target whose CPU + // architecture is unknown. This must not throw and should resolve to + // the same path as an explicit arm64 target. Regression test for the + // assertion crash introduced by the PlatformType + CpuArch refactor. + expect( + artifacts.getArtifactPath( + Artifact.flutterPatchedSdkPath, + platform: const TargetPlatform(.android, .unknown), + mode: BuildMode.release, + ), + artifacts.getArtifactPath( + Artifact.flutterPatchedSdkPath, + platform: const TargetPlatform(.android, .arm64), + mode: BuildMode.release, + ), + ); }); testWithoutContext( @@ -335,7 +365,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), xcframeworkPath, @@ -347,7 +377,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), throwsToolExit( @@ -364,7 +394,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), throwsToolExit( @@ -389,7 +419,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), fileSystem.path.join(xcframeworkPath, 'macos-arm64_x86_64', 'FlutterMacOS.framework'), @@ -404,7 +434,7 @@ void main() { testWithoutContext('getArtifactPath', () { final String xcframeworkPath = artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ); expect( @@ -414,7 +444,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -426,7 +456,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -455,7 +485,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -464,7 +494,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, environmentType: EnvironmentType.physical, ), @@ -473,7 +503,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ), fileSystem.path.join('/out', 'android_debug_unopt', 'Flutter.xcframework'), @@ -572,21 +602,21 @@ void main() { expect( () => webArtifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), throwsToolExit(message: failureMessage), ); expect( () => webArtifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), throwsToolExit(message: failureMessage), ); expect( () => webArtifacts.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), throwsToolExit(message: failureMessage), ); @@ -602,7 +632,7 @@ void main() { expect( webArtifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join( '/flutter', @@ -617,21 +647,21 @@ void main() { expect( webArtifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join('/flutter', 'prebuilts', 'linux-x64', 'dart-sdk'), ); expect( webArtifacts.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join('/flutter', 'prebuilts', 'linux-x64', 'dart-sdk', 'bin', 'dart'), ); expect( webArtifacts.getArtifactPath( Artifact.engineDartAotRuntime, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join( '/flutter', @@ -654,7 +684,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), throwsToolExit(message: failureMessage), ); @@ -669,7 +699,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join('/out', 'host_debug_unopt', 'dart-sdk'), ); @@ -677,11 +707,14 @@ void main() { testWithoutContext('getEngineType', () { expect( - artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), + artifacts.getEngineType(const TargetPlatform(.android, .armv7), BuildMode.debug), + 'android_debug_unopt', + ); + expect( + artifacts.getEngineType(const TargetPlatform(.ios, .arm64), BuildMode.release), 'android_debug_unopt', ); - expect(artifacts.getEngineType(TargetPlatform.ios, BuildMode.release), 'android_debug_unopt'); - expect(artifacts.getEngineType(TargetPlatform.darwin), 'android_debug_unopt'); + expect(artifacts.getEngineType(const TargetPlatform(.macos, .x64)), 'android_debug_unopt'); }); testWithoutContext('Looks up dart.exe on windows platforms', () async { @@ -764,7 +797,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join('/flutter', 'prebuilts', 'windows-x64', 'dart-sdk', 'bin', 'dart.exe'), ); @@ -802,7 +835,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), fileSystem.path.join('/flutter', 'prebuilts', 'macos-x64', 'dart-sdk', 'bin', 'dart'), ); diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart index ac44f4319ad6a..fd4a60443758e 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart @@ -38,7 +38,7 @@ void main() { packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), flavor: flavor, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); return bundle; } diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart index e4f3c165477da..8fd2a465042b8 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart @@ -65,7 +65,7 @@ $fontsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); for (final packageName in packages) { @@ -120,7 +120,7 @@ $fontsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart index d1b2c97ac8509..412c47fc46cc0 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart @@ -87,7 +87,7 @@ $assetsSection await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flavor: flavor, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); for (final packageName in packages) { @@ -153,7 +153,7 @@ $assetsSection expect( () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), throwsToolExit(message: 'resolves to a location outside the package directory'), ); @@ -178,7 +178,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -209,7 +209,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -711,7 +711,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( @@ -803,7 +803,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); }, overrides: { 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 e332c6c2b710d..1bbc7df16c0ef 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -47,7 +47,7 @@ void main() { expect( await ab.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -70,7 +70,7 @@ void main() { final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(bundle.entries.keys, unorderedEquals(['AssetManifest.bin'])); const expectedBinAssetManifest = {}; @@ -120,7 +120,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( @@ -163,7 +163,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -182,7 +182,7 @@ flutter: expect(bundle.needsBuild(), true); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -223,7 +223,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -254,7 +254,7 @@ name: my_app''') expect(bundle.needsBuild(), true); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -295,7 +295,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -350,7 +350,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -401,7 +401,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -462,7 +462,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( bundle.entries.keys, @@ -486,7 +486,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( @@ -540,7 +540,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), throwsToolExit( message: @@ -580,7 +580,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), throwsToolExit( message: @@ -625,7 +625,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(bundle.entries['my-asset.txt']!.content.isModified, isTrue); @@ -634,7 +634,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(bundle.entries['my-asset.txt']!.content.isModified, isFalse); @@ -652,7 +652,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(bundle.entries['my-asset.txt']!.content.isModified, isTrue); @@ -682,7 +682,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); expect( @@ -721,7 +721,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); expect( @@ -774,7 +774,7 @@ flutter: await writeBundle( directory, const {}, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -803,7 +803,7 @@ assets: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final AssetBundleEntry? fontManifest = bundle.entries['FontManifest.json']; @@ -811,7 +811,7 @@ assets: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(fontManifest, bundle.entries['FontManifest.json']); @@ -843,7 +843,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -878,7 +878,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -931,7 +931,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -939,7 +939,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -994,7 +994,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ), 0, ); @@ -1002,7 +1002,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -1156,7 +1156,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ), 0, ); @@ -1164,7 +1164,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -1215,7 +1215,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -1258,7 +1258,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect( @@ -1315,7 +1315,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 1, ); @@ -1350,7 +1350,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 1, ); @@ -1395,7 +1395,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -1437,7 +1437,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -1487,7 +1487,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), 0, ); @@ -1529,7 +1529,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ), throwsToolExit( message: diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart index 91f24c632dcdf..3b58ecb7b5b74 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart @@ -87,7 +87,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -135,7 +135,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -179,7 +179,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -219,7 +219,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final expectedManifest = >>{ @@ -287,7 +287,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final expectedAssetManifest = >>{ diff --git a/packages/flutter_tools/test/general.shard/asset_test.dart b/packages/flutter_tools/test/general.shard/asset_test.dart index eb0e666d4d79f..28f7505a45566 100644 --- a/packages/flutter_tools/test/general.shard/asset_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_test.dart @@ -96,7 +96,7 @@ dependencies: packageConfigPath: packageConfigPath, manifestPath: manifestPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.directory('main')), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(assetBundle.entries, contains('FontManifest.json')); @@ -255,7 +255,7 @@ flutter: packageConfigPath: packageConfigPath, manifestPath: manifestPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.directory('main')), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(assetBundle.entries, contains('FontManifest.json')); @@ -300,7 +300,7 @@ flutter: manifestPath: manifestPath, // file doesn't exist packageConfigPath: packageConfigPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.file(manifestPath).parent), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(assetBundle.wasBuiltOnce(), true); @@ -343,7 +343,7 @@ flutter: await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.android_arm, + targetPlatform: const TargetPlatform(.android, .armv7), flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); @@ -387,7 +387,7 @@ flutter: await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); @@ -416,7 +416,7 @@ flutter: final int result = await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(result, isNot(0)); expect( @@ -446,7 +446,7 @@ flutter: final int result = await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); expect(result, isNot(0)); expect( 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 2f88ba4a34670..bb47b4200f27d 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -43,7 +43,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_x64, + platform: const TargetPlatform(.android, .x64), mode: BuildMode.release, ), '--additional_arg', @@ -52,7 +52,7 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), + snapshotType: SnapshotType(const TargetPlatform(.android, .x64), BuildMode.release), additionalArgs: ['--additional_arg'], ); expect(result, 0); @@ -61,7 +61,7 @@ void main() { testWithoutContext('iOS arm64', () async { final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ); processManager.addCommand( @@ -69,8 +69,7 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), - cpuArch: CpuArch.arm64, + snapshotType: SnapshotType(const TargetPlatform(.ios, .arm64), BuildMode.release), additionalArgs: ['--additional_arg'], ); expect(result, 0); @@ -82,7 +81,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_x64, + platform: const TargetPlatform(.android, .x64), mode: BuildMode.release, ), '--strip', @@ -92,7 +91,7 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), + snapshotType: SnapshotType(const TargetPlatform(.android, .x64), BuildMode.release), additionalArgs: ['--strip'], ); @@ -129,8 +128,7 @@ void main() { expect( await snapshotter.build( - platform: TargetPlatform.ios, - cpuArch: CpuArch.arm64, + platform: const TargetPlatform(.ios, .arm64), sdkRoot: 'path/to/sdk', buildMode: BuildMode.debug, mainPath: 'main.dill', @@ -146,7 +144,7 @@ void main() { expect( await snapshotter.build( - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), buildMode: BuildMode.debug, mainPath: 'main.dill', outputPath: outputPath, @@ -161,7 +159,7 @@ void main() { expect( await snapshotter.build( - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), buildMode: BuildMode.debug, mainPath: 'main.dill', outputPath: outputPath, @@ -176,7 +174,7 @@ void main() { final String debugPath = fileSystem.path.join('foo', 'app.ios-arm64.symbols'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.profile, ); processManager.addCommands([ @@ -220,11 +218,10 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, - cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', splitDebugInfo: 'foo', dartObfuscation: false, @@ -238,7 +235,7 @@ void main() { final String outputPath = fileSystem.path.join('build', 'foo'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.profile, ); processManager.addCommands([ @@ -280,11 +277,10 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, - cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: true, ); @@ -297,7 +293,7 @@ void main() { final String outputPath = fileSystem.path.join('build', 'foo'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.release, ); processManager.addCommands([ @@ -338,11 +334,10 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, - cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: false, ); @@ -358,7 +353,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.release, ), '--deterministic', @@ -372,7 +367,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -391,7 +386,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.release, ), '--deterministic', @@ -408,7 +403,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -427,7 +422,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.release, ), '--deterministic', @@ -442,7 +437,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -462,7 +457,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.release, ), '--deterministic', @@ -476,7 +471,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -496,7 +491,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -508,7 +503,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -526,7 +521,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -538,7 +533,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart index 57fcd7f4dad76..ad6ffe52076c0 100644 --- a/packages/flutter_tools/test/general.shard/build_info_test.dart +++ b/packages/flutter_tools/test/general.shard/build_info_test.dart @@ -21,51 +21,47 @@ void main() { group('Validate build number', () { testWithoutContext('CFBundleVersion for iOS', () async { - String? buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, 'xyz', logger); + String? buildName = validatedBuildNumberForPlatform(PlatformType.ios, 'xyz', logger); expect(buildName, isNull); - buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '0.0.1', logger); + buildName = validatedBuildNumberForPlatform(PlatformType.ios, '0.0.1', logger); expect(buildName, '0.0.1'); - buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '123.xyz', logger); + buildName = validatedBuildNumberForPlatform(PlatformType.ios, '123.xyz', logger); expect(buildName, '123'); - buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '123.456.xyz', logger); + buildName = validatedBuildNumberForPlatform(PlatformType.ios, '123.456.xyz', logger); expect(buildName, '123.456'); }); testWithoutContext('versionCode for Android', () async { String? buildName = validatedBuildNumberForPlatform( - TargetPlatform.android_arm, + PlatformType.android, '123.abc+-', logger, ); expect(buildName, '123'); - buildName = validatedBuildNumberForPlatform(TargetPlatform.android_arm, 'abc', logger); + buildName = validatedBuildNumberForPlatform(PlatformType.android, 'abc', logger); expect(buildName, '1'); }); }); group('Validate build name', () { testWithoutContext('CFBundleShortVersionString for iOS', () async { - String? buildName = validatedBuildNameForPlatform(TargetPlatform.ios, 'xyz', logger); + String? buildName = validatedBuildNameForPlatform(PlatformType.ios, 'xyz', logger); expect(buildName, isNull); - buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '0.0.1', logger); + buildName = validatedBuildNameForPlatform(PlatformType.ios, '0.0.1', logger); expect(buildName, '0.0.1'); - buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '123.456.xyz', logger); + buildName = validatedBuildNameForPlatform(PlatformType.ios, '123.456.xyz', logger); expect(logger.traceText, contains('Invalid build-name')); expect(buildName, '123.456.0'); - buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '123.xyz', logger); + buildName = validatedBuildNameForPlatform(PlatformType.ios, '123.xyz', logger); expect(buildName, '123.0.0'); }); testWithoutContext('versionName for Android', () async { - String? buildName = validatedBuildNameForPlatform( - TargetPlatform.android_arm, - '123.abc+-', - logger, - ); + String? buildName = validatedBuildNameForPlatform(PlatformType.android, '123.abc+-', logger); expect(buildName, '123.abc+-'); - buildName = validatedBuildNameForPlatform(TargetPlatform.android_arm, 'abc+-', logger); + buildName = validatedBuildNameForPlatform(PlatformType.android, 'abc+-', logger); expect(buildName, 'abc+-'); }); @@ -106,11 +102,25 @@ void main() { expect(CpuArch.x64.darwinArchName, 'x86_64'); }); - testWithoutContext('getNameForTargetPlatform on Darwin arches', () { - expect(TargetPlatform.ios.getName(cpuArch: CpuArch.arm64), 'ios-arm64'); - expect(TargetPlatform.ios.getName(cpuArch: CpuArch.armv7), 'ios-armv7'); - expect(TargetPlatform.ios.getName(cpuArch: CpuArch.x64), 'ios-x86_64'); - expect(TargetPlatform.android.getName(), isNot(contains('ios'))); + testWithoutContext('getName derives the canonical name from the platform and arch', () { + // iOS and macOS include the CPU architecture in their name (e.g. + // `ios-arm64`, `darwin-x64`), falling back to the bare platform name when + // the architecture is unknown. + expect(const TargetPlatform(.ios, .arm64).getName(), 'ios-arm64'); + expect(const TargetPlatform(.ios, .x64).getName(), 'ios-x64'); + expect(const TargetPlatform(.ios, .unknown).getName(), 'ios'); + expect(const TargetPlatform(.macos, .arm64).getName(), 'darwin-arm64'); + expect(const TargetPlatform(.macos, .x64).getName(), 'darwin-x64'); + expect(const TargetPlatform(.macos, .unknown).getName(), 'darwin'); + // Desktop platforms follow the `-` convention. + expect(const TargetPlatform(.linux, .x64).getName(), 'linux-x64'); + expect(const TargetPlatform(.linux, .arm64).getName(), 'linux-arm64'); + expect(const TargetPlatform(.windows, .arm64).getName(), 'windows-arm64'); + // Android has its own per-arch names. + expect(const TargetPlatform(.android, .armv7).getName(), 'android-arm'); + expect(const TargetPlatform(.android, .arm64).getName(), 'android-arm64'); + expect(const TargetPlatform(.android, .x64).getName(), 'android-x64'); + expect(const TargetPlatform(.android, .unknown).getName(), 'android'); }); testUsingContext( diff --git a/packages/flutter_tools/test/general.shard/build_system/source_test.dart b/packages/flutter_tools/test/general.shard/build_system/source_test.dart index 653b04b291b71..0b162ab1b3c81 100644 --- a/packages/flutter_tools/test/general.shard/build_system/source_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/source_test.dart @@ -111,7 +111,7 @@ void main() { globals.fs.file(path).createSync(recursive: true); const fizzSource = Source.artifact( Artifact.windowsDesktopPath, - platform: TargetPlatform.windows_x64, + platform: TargetPlatform(.windows, .x64), ); fizzSource.accept(visitor); @@ -277,7 +277,7 @@ void main() { const fizzSource = Source.artifact( Artifact.windowsDesktopPath, - platform: TargetPlatform.windows_x64, + platform: TargetPlatform(.windows, .x64), ); fizzSource.accept(visitor); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart index 9ccb733787282..eb9190b943729 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart @@ -174,7 +174,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -187,7 +187,7 @@ void main() { environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); await androidAot.build(environment); @@ -210,7 +210,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -225,7 +225,7 @@ void main() { environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); await androidAot.build(environment); @@ -252,7 +252,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -269,7 +269,7 @@ void main() { environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release).build(environment); + await const AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release).build(environment); }); testUsingContext( @@ -294,7 +294,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm64, + platform: const TargetPlatform(.android, .arm64), mode: BuildMode.release, ), '--deterministic', @@ -311,7 +311,10 @@ void main() { environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release).build(environment); + await const AndroidAot( + TargetPlatform(.android, .arm64), + BuildMode.release, + ).build(environment); }, ); @@ -326,7 +329,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); // Create required files. environment.buildDir 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 427cde4f21c0b..fb23a80562f99 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 @@ -844,9 +844,9 @@ flutter: testUsingContext( platform, () async { - final TargetPlatform targetPlatform = platform == 'android' - ? TargetPlatform.ios - : TargetPlatform.android; + final targetPlatform = platform == 'android' + ? const TargetPlatform(.ios, .arm64) + : const TargetPlatform(.android, .unknown); final bool didInclude = await setupAndBuildPlatformAsset(platform, targetPlatform); expect( 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 64f77221e483f..9fef520525d6d 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 @@ -49,7 +49,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: TargetPlatform.android_arm.getName(), + kTargetPlatform: const TargetPlatform(.android, .armv7).getName(), }, inputs: {}, artifacts: artifacts, @@ -62,7 +62,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: TargetPlatform.ios.getName(), + kTargetPlatform: const TargetPlatform(.ios, .arm64).getName(), }, inputs: {}, artifacts: artifacts, @@ -88,7 +88,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ); processManager.addCommands([ @@ -130,7 +130,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ); processManager.addCommands([ @@ -175,7 +175,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ); processManager.addCommands([ @@ -220,7 +220,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ); processManager.addCommands([ @@ -266,7 +266,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ); processManager.addCommands([ @@ -314,7 +314,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.debug, ); processManager.addCommands([ @@ -362,7 +362,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ); processManager.addCommands([ @@ -393,7 +393,7 @@ void main() { await const KernelSnapshot().build( androidEnvironment - ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() + ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kTrackWidgetCreation] = 'false', ); @@ -411,7 +411,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android, + platform: const TargetPlatform(.android, .unknown), mode: BuildMode.debug, ); processManager.addCommands([ @@ -453,7 +453,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.debug, ); fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true); @@ -487,7 +487,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = TargetPlatform.ios.getName() + ..defines[kTargetPlatform] = const TargetPlatform(.ios, .arm64).getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' ..defines[kXcodeConfiguration] = 'Debug-chocolate' @@ -511,7 +511,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ); fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true); @@ -544,7 +544,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() + ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' ..defines[kXcodeConfiguration] = 'Debug-chocolate' @@ -568,7 +568,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ); processManager.addCommands([ @@ -600,7 +600,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() + ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kDartDefines] = base64Encode(utf8.encode('FLUTTER_APP_FLAVOR=vanilla')) ..defines[kFlavor] = 'strawberry' @@ -624,7 +624,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.debug.cliName, - kTargetPlatform: TargetPlatform.android_arm.getName(), + kTargetPlatform: const TargetPlatform(.android, .armv7).getName(), }, processManager: processManager, artifacts: artifacts, @@ -634,7 +634,7 @@ void main() { final String build = testEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.debug, ); processManager.addCommands([ @@ -678,7 +678,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ), '--deterministic', @@ -693,7 +693,7 @@ void main() { androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true); androidEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment); + await const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); @@ -706,7 +706,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ), '--deterministic', @@ -723,7 +723,7 @@ void main() { androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true); androidEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment); + await const AotElfRelease(TargetPlatform(.android, .armv7)).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); @@ -732,7 +732,7 @@ void main() { androidEnvironment.defines.remove(kBuildMode); expect( - const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment), + const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment), throwsA(isA()), ); }); @@ -741,7 +741,7 @@ void main() { androidEnvironment.defines.remove(kTargetPlatform); expect( - const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment), + const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment), throwsA(isA()), ); }); @@ -803,7 +803,7 @@ void main() { FakeCommand( command: [ // This path is not known by the cache due to the iOS gen_snapshot split. - 'Artifact.genSnapshotArm64.TargetPlatform.ios.profile', + 'Artifact.genSnapshotArm64.ios.profile', '--deterministic', '--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64.json', '--trace-precompiler-to=code_size_1/trace.arm64.json', @@ -867,7 +867,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: TargetPlatform.android_arm, + platform: const TargetPlatform(.android, .armv7), mode: BuildMode.profile, ), '--deterministic', @@ -883,7 +883,7 @@ void main() { ), ]); - await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment); + await const AotElfRelease(TargetPlatform(.android, .armv7)).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart index 151319ce2b490..83e2981ff6122 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart @@ -38,7 +38,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( @@ -72,7 +72,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( @@ -105,7 +105,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); + const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( 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 72aeb07d84ca6..cde0c70bef988 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 @@ -128,7 +128,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); expect( @@ -160,7 +160,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); expect(logger.errorText, isEmpty); @@ -181,7 +181,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); expect(logger.errorText, isEmpty); @@ -202,7 +202,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); expect( @@ -230,7 +230,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); final stdinSink = CompleterIOSink(); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -280,7 +280,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); final stdinSink = CompleterIOSink(); @@ -312,7 +312,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); final stdinSink = CompleterIOSink(); @@ -330,8 +330,8 @@ void main() { }); for (final platform in [ - TargetPlatform.android_arm, - TargetPlatform.web_javascript, + const TargetPlatform(.android, .armv7), + const TargetPlatform(.web, .unknown), ]) { testWithoutContext('Non-constant instances $platform', () async { final Environment environment = createEnvironment({ @@ -382,7 +382,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android_arm64, + targetPlatform: const TargetPlatform(.android, .arm64), ); addConstFinderInvocation( @@ -425,7 +425,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); addConstFinderInvocation( @@ -469,7 +469,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); final stdinSink = CompleterIOSink(); @@ -501,7 +501,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); final stdinSink = CompleterIOSink(throwOnAdd: true); @@ -535,7 +535,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -568,7 +568,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); @@ -611,7 +611,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); @@ -652,7 +652,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ); addConstFinderInvocation(appDill.path, exitCode: -1); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart index bd5dc8e56291f..3736b6f2c8b00 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart @@ -862,7 +862,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFramework.ios.debug.EnvironmentType.physical', outputDir.path, ], ); @@ -875,7 +875,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFrameworkDsym.ios.debug.EnvironmentType.physical', outputDir.path, ], ); @@ -888,7 +888,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFrameworkDsym.ios.debug.EnvironmentType.physical', outputDir.path, ], exitCode: 1, @@ -932,7 +932,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator', + 'Artifact.flutterFramework.ios.debug.EnvironmentType.simulator', outputDir.path, ], onRun: (_) => binary.createSync(recursive: true), @@ -979,7 +979,7 @@ void main() { final Directory dSYM = fileSystem.directory( artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.debug, environmentType: EnvironmentType.physical, ), @@ -1337,7 +1337,7 @@ void main() { final Directory dSYM = fileSystem.directory( artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), mode: BuildMode.debug, environmentType: EnvironmentType.physical, ), diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart index 5f3dbf7dbca86..61d907d9d6f90 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart @@ -38,7 +38,7 @@ void main() { ); testEnvironment.buildDir.createSync(recursive: true); - await const UnpackLinux(TargetPlatform.linux_x64).build(testEnvironment); + await const UnpackLinux(TargetPlatform(.linux, .x64)).build(testEnvironment); expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_gtk.so'), exists); expect(fileSystem.file('linux/flutter/ephemeral/unrelated-stuff'), isNot(exists)); @@ -46,12 +46,12 @@ void main() { // Check if the target files are copied correctly. final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), mode: BuildMode.debug, ); expect(fileSystem.file('linux/flutter/ephemeral/$headersPathForX64/foo.h'), exists); @@ -59,11 +59,11 @@ void main() { final String icuDataPathForX64 = artifacts.getArtifactPath( Artifact.icuData, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), ); final String icuDataPathForArm64 = artifacts.getArtifactPath( Artifact.icuData, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), ); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForX64'), exists); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForArm64'), isNot(exists)); @@ -89,7 +89,7 @@ void main() { ); testEnvironment.buildDir.createSync(recursive: true); - await const UnpackLinux(TargetPlatform.linux_arm64).build(testEnvironment); + await const UnpackLinux(TargetPlatform(.linux, .arm64)).build(testEnvironment); expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_gtk.so'), exists); expect(fileSystem.file('linux/flutter/ephemeral/unrelated-stuff'), isNot(exists)); @@ -97,12 +97,12 @@ void main() { // Check if the target files are copied correctly. final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), mode: BuildMode.debug, ); expect(fileSystem.file('linux/flutter/ephemeral/$headersPathForX64/foo.h'), isNot(exists)); @@ -110,11 +110,11 @@ void main() { final String icuDataPathForX64 = artifacts.getArtifactPath( Artifact.icuData, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), ); final String icuDataPathForArm64 = artifacts.getArtifactPath( Artifact.icuData, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), ); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForX64'), isNot(exists)); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForArm64'), exists); @@ -147,7 +147,7 @@ void main() { testEnvironment.buildDir.childFile('app.dill').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const DebugBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); + await const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); final Directory output = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -203,7 +203,7 @@ flutter: flavorFileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); writePackageConfigFiles(directory: flavorFileSystem.currentDirectory, mainLibName: 'example'); - await const DebugBundleLinuxAssets(TargetPlatform.linux_x64).build(environment); + await const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(environment); final Uint8List assetManifestData = environment.outputDir .childDirectory('flutter_assets') @@ -225,11 +225,11 @@ flutter: testWithoutContext("DebugBundleLinuxAssets' name depends on target platforms", () async { expect( - const DebugBundleLinuxAssets(TargetPlatform.linux_x64).name, + const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, 'debug_bundle_linux-x64_assets', ); expect( - const DebugBundleLinuxAssets(TargetPlatform.linux_arm64).name, + const DebugBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, 'debug_bundle_linux-arm64_assets', ); }); @@ -252,8 +252,10 @@ flutter: testEnvironment.buildDir.childFile('app.so').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const LinuxAotBundle(AotElfProfile(TargetPlatform.linux_x64)).build(testEnvironment); - await const ProfileBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); + await const LinuxAotBundle( + AotElfProfile(TargetPlatform(.linux, .x64)), + ).build(testEnvironment); + await const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); final Directory libDir = testEnvironment.outputDir.childDirectory('lib'); final Directory assetsDir = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -270,11 +272,11 @@ flutter: testWithoutContext("ProfileBundleLinuxAssets' name depends on target platforms", () async { expect( - const ProfileBundleLinuxAssets(TargetPlatform.linux_x64).name, + const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, 'profile_bundle_linux-x64_assets', ); expect( - const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64).name, + const ProfileBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, 'profile_bundle_linux-arm64_assets', ); }); @@ -297,8 +299,10 @@ flutter: testEnvironment.buildDir.childFile('app.so').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const LinuxAotBundle(AotElfRelease(TargetPlatform.linux_x64)).build(testEnvironment); - await const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); + await const LinuxAotBundle( + AotElfRelease(TargetPlatform(.linux, .x64)), + ).build(testEnvironment); + await const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); final Directory libDir = testEnvironment.outputDir.childDirectory('lib'); final Directory assetsDir = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -315,11 +319,11 @@ flutter: testWithoutContext("ReleaseBundleLinuxAssets' name depends on target platforms", () async { expect( - const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64).name, + const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, 'release_bundle_linux-x64_assets', ); expect( - const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64).name, + const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, 'release_bundle_linux-arm64_assets', ); }); @@ -328,12 +332,12 @@ flutter: void setUpCacheDirectory(FileSystem fileSystem, Artifacts artifacts) { final String desktopPathForX64 = artifacts.getArtifactPath( Artifact.linuxDesktopPath, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), mode: BuildMode.debug, ); final String desktopPathForArm64 = artifacts.getArtifactPath( Artifact.linuxDesktopPath, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), mode: BuildMode.debug, ); fileSystem.file('$desktopPathForX64/unrelated-stuff').createSync(recursive: true); @@ -343,22 +347,29 @@ void setUpCacheDirectory(FileSystem fileSystem, Artifacts artifacts) { final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_x64, + platform: const TargetPlatform(.linux, .x64), mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), mode: BuildMode.debug, ); fileSystem.file('$headersPathForX64/foo.h').createSync(recursive: true); fileSystem.file('$headersPathForArm64/foo.h').createSync(recursive: true); fileSystem - .file(artifacts.getArtifactPath(Artifact.icuData, platform: TargetPlatform.linux_x64)) + .file( + artifacts.getArtifactPath( + Artifact.icuData, + platform: const TargetPlatform(.linux, .x64), + ), + ) .createSync(); fileSystem - .file(artifacts.getArtifactPath(Artifact.icuData, platform: TargetPlatform.linux_arm64)) + .file( + artifacts.getArtifactPath(Artifact.icuData, platform: const TargetPlatform(.linux, .arm64)), + ) .createSync(); fileSystem diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart index a580bb35f82e6..95fe7b8428bc0 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart @@ -101,7 +101,7 @@ void main() { .directory( artifacts.getArtifactPath( Artifact.flutterMacOSFrameworkDsym, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.release, ), ) @@ -125,7 +125,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterMacOSFrameworkDsym.TargetPlatform.darwin.release', + 'Artifact.flutterMacOSFrameworkDsym.darwin-x64.release', environment.outputDir.path, ], ); @@ -358,7 +358,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterMacOSFrameworkDsym.TargetPlatform.darwin.release', + 'Artifact.flutterMacOSFrameworkDsym.darwin-x64.release', environment.outputDir.path, ], exitCode: 1, @@ -421,7 +421,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ), ) @@ -430,7 +430,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ), ) @@ -482,7 +482,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ), ) @@ -491,7 +491,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: TargetPlatform.darwin, + platform: const TargetPlatform(.macos, .x64), mode: BuildMode.debug, ), ) @@ -813,7 +813,7 @@ void main() { processManager.addCommands([ FakeCommand( command: [ - 'Artifact.genSnapshotArm64.TargetPlatform.darwin.release', + 'Artifact.genSnapshotArm64.darwin-x64.release', '--deterministic', '--snapshot_kind=app-aot-macho-dylib', '--macho=${environment.buildDir.childFile('arm64/App.framework/App').path}', @@ -826,7 +826,7 @@ void main() { ), FakeCommand( command: [ - 'Artifact.genSnapshotX64.TargetPlatform.darwin.release', + 'Artifact.genSnapshotX64.darwin-x64.release', '--deterministic', '--snapshot_kind=app-aot-macho-dylib', '--macho=${environment.buildDir.childFile('x86_64/App.framework/App').path}', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart index 94dda581a3f96..7187c93bdf618 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart @@ -74,7 +74,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ), true, ); @@ -114,7 +114,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), ), true, ); @@ -155,7 +155,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.android, + targetPlatform: const TargetPlatform(.android, .unknown), ), true, ); @@ -194,7 +194,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(notFragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ), true, ); @@ -233,7 +233,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(notFragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), ); fail('unreachable'); } on ShaderCompilerException catch (e) { @@ -289,7 +289,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -340,7 +340,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.tester); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.tester, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -391,7 +391,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -442,7 +442,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.tester); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.tester, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -493,7 +493,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -542,7 +542,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.web_javascript); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.web, .unknown)); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -601,7 +601,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -659,7 +659,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -715,7 +715,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -770,7 +770,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(TargetPlatform.android); + developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); final shaderContent = DevFSByteContent(Uint8List.fromList([1, 2, 3, 4])); @@ -820,7 +820,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), ), throwsToolExit(message: 'Impeller shader compiler was blocked by security policy.'), ); @@ -867,7 +867,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), ), throwsToolExit(message: 'Impeller shader compiler was blocked by security policy.'), ); @@ -913,7 +913,7 @@ void main() { final bool success = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), fatal: false, ); @@ -952,7 +952,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), ), throwsA( isA().having( @@ -1012,7 +1012,7 @@ void main() { final bool success1 = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), fatal: false, ); expect(success1, false); @@ -1026,7 +1026,7 @@ void main() { final bool success2 = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), fatal: false, ); expect(success2, false); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart index 10f17402ba58f..d24e6049f7fb1 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart @@ -83,7 +83,7 @@ name: my_app fakeFlutterVersion: FakeFlutterVersion(), ); commandArgs = [ - 'Artifact.engineDartBinary.TargetPlatform.web_javascript', + 'Artifact.engineDartBinary.web-javascript', 'compile', 'wasm', '--packages=/.dart_tool/package_config.json', 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 a51af6e45bfd6..c648e331a2374 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 @@ -28,7 +28,7 @@ import '../../../src/testbed.dart'; import '../../../src/throwing_pub.dart'; const _kDart2jsLinuxArgs = [ - 'Artifact.engineDartBinary.TargetPlatform.web_javascript', + 'Artifact.engineDartBinary.web-javascript', 'compile', 'js', '--platform-binaries=HostArtifact.webPlatformKernelFolder', @@ -44,7 +44,7 @@ const _kStandardFlutterWebDefines = [ ]; const _kDart2WasmLinuxArgs = [ - 'Artifact.engineDartBinary.TargetPlatform.web_javascript', + 'Artifact.engineDartBinary.web-javascript', 'compile', 'wasm', '--packages=/.dart_tool/package_config.json', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart index 4a41adc5b15e1..a2345146ae412 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart @@ -38,17 +38,17 @@ void main() { final String windowsDesktopPath = artifacts.getArtifactPath( Artifact.windowsDesktopPath, - platform: TargetPlatform.windows_x64, + platform: const TargetPlatform(.windows, .x64), mode: BuildMode.debug, ); final String windowsCppClientWrapper = artifacts.getArtifactPath( Artifact.windowsCppClientWrapper, - platform: TargetPlatform.windows_x64, + platform: const TargetPlatform(.windows, .x64), mode: BuildMode.debug, ); final String icuData = artifacts.getArtifactPath( Artifact.icuData, - platform: TargetPlatform.windows_x64, + platform: const TargetPlatform(.windows, .x64), ); final requiredFiles = [ '$windowsDesktopPath\\flutter_export.h', @@ -70,7 +70,7 @@ void main() { } fileSystem.directory('windows').createSync(); - await const UnpackWindows(TargetPlatform.windows_x64).build(environment); + await const UnpackWindows(TargetPlatform(.windows, .x64)).build(environment); // Output files are copied correctly. expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_export.h'), exists); @@ -165,7 +165,7 @@ void main() { environment.buildDir.childFile('app.dill').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const DebugBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); + await const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); // Depfile is created and dill is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); @@ -214,7 +214,7 @@ flutter: flavorFileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); writePackageConfigFiles(directory: flavorFileSystem.currentDirectory, mainLibName: 'example'); - await const DebugBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); + await const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); final Uint8List assetManifestData = environment.outputDir .childDirectory('flutter_assets') @@ -249,8 +249,10 @@ flutter: environment.buildDir.childFile('app.so').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const WindowsAotBundle(AotElfProfile(TargetPlatform.windows_x64)).build(environment); - await const ProfileBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); + await const WindowsAotBundle( + AotElfProfile(TargetPlatform(.windows, .x64)), + ).build(environment); + await const ProfileBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); // Depfile is created and so is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); @@ -278,8 +280,10 @@ flutter: environment.buildDir.childFile('app.so').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const WindowsAotBundle(AotElfRelease(TargetPlatform.windows_x64)).build(environment); - await const ReleaseBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); + await const WindowsAotBundle( + AotElfRelease(TargetPlatform(.windows, .x64)), + ).build(environment); + await const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); // Depfile is created and so is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); 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 c7e0cddf97131..d824a849b32d3 100644 --- a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart @@ -44,7 +44,7 @@ void main() { }); await BundleBuilder().build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), @@ -119,7 +119,7 @@ void main() { await writeBundle( bundleDir, bundle.entries, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), impellerStatus: ImpellerStatus.platformDefault, processManager: processManager, fileSystem: fileSystem, @@ -140,7 +140,7 @@ void main() { () { expect( () => BundleBuilder().build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: 'lib/main.dart', @@ -177,7 +177,7 @@ void main() { }); await BundleBuilder().build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildInfo: const BuildInfo( BuildMode.debug, null, @@ -200,7 +200,7 @@ void main() { expect(env, isNotNull); expect(env!.defines[kBuildMode], 'debug'); - expect(env!.defines[kTargetPlatform], 'ios'); + expect(env!.defines[kTargetPlatform], 'ios-arm64'); expect(env!.defines[kTargetFile], mainPath); expect(env!.defines[kTrackWidgetCreation], 'true'); expect(env!.defines[kFrontendServerStarterPath], 'path/to/frontend_server_starter.dart'); @@ -312,7 +312,7 @@ void main() { } }); await BundleBuilder().build( - platform: TargetPlatform.ios, + platform: const TargetPlatform(.ios, .arm64), buildInfo: BuildInfo.release, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), diff --git a/packages/flutter_tools/test/general.shard/cold_test.dart b/packages/flutter_tools/test/general.shard/cold_test.dart index b64e4c1a55e69..566a4153b8ed5 100644 --- a/packages/flutter_tools/test/general.shard/cold_test.dart +++ b/packages/flutter_tools/test/general.shard/cold_test.dart @@ -209,7 +209,7 @@ class FakeDevice extends Fake implements Device { String get displayName => name; @override - Future get targetPlatform async => TargetPlatform.tester; + Future get targetPlatform async => const TargetPlatform(.tester, .unknown); @override DartDevelopmentService get dds => FakeDartDevelopmentService(); @@ -259,7 +259,7 @@ class TestFlutterDevice extends FlutterDevice { required this.exception, required ResidentCompiler generator, }) : super( - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), device, buildInfo: BuildInfo.debug, generator: generator, diff --git a/packages/flutter_tools/test/general.shard/compile_expression_test.dart b/packages/flutter_tools/test/general.shard/compile_expression_test.dart index 2cfece7bd6466..b2827c898acd7 100644 --- a/packages/flutter_tools/test/general.shard/compile_expression_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_expression_test.dart @@ -36,7 +36,7 @@ void main() { fileSystem = MemoryFileSystem.test() ..file(Artifact.flutterPatchedSdkPath.toString()).createSync(); generator = const ResidentCompilerFactory().create( - targetPlatform: .tester, + targetPlatform: const TargetPlatform(.tester, .unknown), buildInfo: BuildInfo.debug, artifacts: Artifacts.test(fileSystem: fileSystem), processManager: processManager, diff --git a/packages/flutter_tools/test/general.shard/compile_test.dart b/packages/flutter_tools/test/general.shard/compile_test.dart index 0be122136050a..00dc4df72901f 100644 --- a/packages/flutter_tools/test/general.shard/compile_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_test.dart @@ -163,7 +163,7 @@ void main() { // Initializing the compiler with includeUnsupportedPlatformLibraryStubs for targets other // than DDC is not currently supported as it's limited for use with the widget previewer. for (final TargetPlatform target in TargetPlatform.values.where( - (e) => e != .web_javascript, + (e) => e != const TargetPlatform(.web, .unknown), )) { try { const ResidentCompilerFactory().create( @@ -190,7 +190,7 @@ void main() { // Initializing the compiler with includeUnsupportedPlatformLibraryStubs for DDC is // supported. const ResidentCompilerFactory().create( - targetPlatform: .web_javascript, + targetPlatform: const TargetPlatform(.web, .unknown), buildInfo: BuildInfo.debug.copyWith(includeUnsupportedPlatformLibraryStubs: true), logger: BufferLogger.test(), processManager: FakeProcessManager.any(), @@ -210,8 +210,8 @@ void main() { final processManager = FakeProcessManager.list([ FakeCommand( command: const [ - 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', + 'Artifact.engineDartAotRuntime.web-javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', '--sdk-root', 'sdkroot/', '--incremental', @@ -285,8 +285,8 @@ void main() { final processManager = FakeProcessManager.list([ FakeCommand( command: const [ - 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', + 'Artifact.engineDartAotRuntime.web-javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', '--sdk-root', 'sdkroot/', '--incremental', diff --git a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart index 51790a14af8e8..c86b705494b6b 100644 --- a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart +++ b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart @@ -118,7 +118,7 @@ void main() { expect(device.name, 'testlabel'); expect(device.platformType, PlatformType.custom); expect(await device.sdkNameAndVersion, 'testsdknameandversion'); - expect(await device.targetPlatform, TargetPlatform.linux_arm64); + expect(await device.targetPlatform, const TargetPlatform(.linux, .arm64)); expect(await device.installApp(linuxApp), true); expect(await device.uninstallApp(linuxApp), true); expect(await device.isLatestBuildInstalled(linuxApp), false); @@ -537,7 +537,7 @@ void main() { final runDebugCompleter = Completer(); final CustomDeviceConfig config = testConfig.copyWith( - platform: TargetPlatform.linux_arm64, + platform: const TargetPlatform(.linux, .arm64), postBuildCommand: const [ 'testpostbuild', r'--buildMode=${buildMode}', @@ -675,12 +675,12 @@ void main() { testWithoutContext('CustomDevice returns correct target platform', () async { final device = CustomDevice( - config: testConfig.copyWith(platform: TargetPlatform.linux_x64), + config: testConfig.copyWith(platform: const TargetPlatform(.linux, .x64)), logger: BufferLogger.test(), processManager: FakeProcessManager.empty(), ); - expect(await device.targetPlatform, TargetPlatform.linux_x64); + expect(await device.targetPlatform, const TargetPlatform(.linux, .x64)); }); testWithoutContext( diff --git a/packages/flutter_tools/test/general.shard/darwin_test.dart b/packages/flutter_tools/test/general.shard/darwin_test.dart index c2cc60c9087d9..738658e22e687 100644 --- a/packages/flutter_tools/test/general.shard/darwin_test.dart +++ b/packages/flutter_tools/test/general.shard/darwin_test.dart @@ -28,10 +28,13 @@ void main() { }); testWithoutContext('fromTargetPlatform', () { expect( - FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.ios), + FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.ios, .arm64)), FlutterDarwinPlatform.ios, ); - expect(FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.android), null); + expect( + FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.android, .unknown)), + null, + ); }); testWithoutContext('fromName', () { expect(FlutterDarwinPlatform.fromName('ios'), FlutterDarwinPlatform.ios); @@ -55,10 +58,13 @@ void main() { }); testWithoutContext('fromTargetPlatform', () { expect( - FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.darwin), + FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.macos, .x64)), FlutterDarwinPlatform.macos, ); - expect(FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.android), null); + expect( + FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.android, .unknown)), + null, + ); }); testWithoutContext('fromName', () { expect(FlutterDarwinPlatform.fromName('macos'), FlutterDarwinPlatform.macos); diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart index 382c94131383d..a7b9d298ce777 100644 --- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart +++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart @@ -467,7 +467,7 @@ class FakeDesktopDevice extends DesktopDevice { String get name => 'dummy'; @override - Future get targetPlatform async => TargetPlatform.tester; + Future get targetPlatform async => const TargetPlatform(.tester, .unknown); @override Future get cpuArch async => CpuArch.unknown; @@ -517,7 +517,7 @@ class FakeMacOSDevice extends MacOSDevice { String get name => 'dummy'; @override - Future get targetPlatform async => TargetPlatform.tester; + Future get targetPlatform async => const TargetPlatform(.tester, .unknown); @override Future isSupported() async => true; diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index a99a17db621ad..ced11f011e57d 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -288,9 +288,9 @@ void main() { isSupportedForProject: false, ); final webDevice = FakeDevice('webby', 'webby') - ..targetPlatform = Future.value(TargetPlatform.web_javascript); + ..targetPlatform = Future.value(const TargetPlatform(.web, .unknown)); final fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay') - ..targetPlatform = Future.value(TargetPlatform.fuchsia_x64); + ..targetPlatform = Future.value(const TargetPlatform(.fuchsia, .x64)); final unconnectedDevice = FakeDevice('ephemeralTwo', 'ephemeralTwo', isConnected: false); final wirelessDevice = FakeDevice( 'ephemeralTwo', diff --git a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart index 594773bebdf9f..14fd7786b689f 100644 --- a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart @@ -535,7 +535,7 @@ class FakeDevice extends Fake implements Device { final DartDevelopmentService dds = FakeDartDevelopmentService(); @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); @override Future getLogReader({ diff --git a/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart index 60a6a7f980a31..cc47482154ad8 100644 --- a/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart @@ -451,5 +451,5 @@ class FakeDevice extends Fake implements Device { final PlatformType platformType = PlatformType.web; @override - Future get targetPlatform async => TargetPlatform.android_arm; + Future get targetPlatform async => const TargetPlatform(.android, .armv7); } diff --git a/packages/flutter_tools/test/general.shard/emulator_test.dart b/packages/flutter_tools/test/general.shard/emulator_test.dart index 5d56a74ff27ef..b3076fa6c862e 100644 --- a/packages/flutter_tools/test/general.shard/emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/emulator_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/emulator.dart'; import 'package:flutter_tools/src/ios/ios_emulators.dart'; diff --git a/packages/flutter_tools/test/general.shard/flutter_platform_test.dart b/packages/flutter_tools/test/general.shard/flutter_platform_test.dart index 5a3e16f017690..8f06e8af1b367 100644 --- a/packages/flutter_tools/test/general.shard/flutter_platform_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_platform_test.dart @@ -539,7 +539,8 @@ class _UnstartableDevice extends Fake implements Device { Future dispose() => Future.value(); @override - Future get targetPlatform => Future.value(TargetPlatform.android); + Future get targetPlatform => + Future.value(const TargetPlatform(.android, .unknown)); @override Future stopApp(ApplicationPackage? app, {String? userIdentifier}) async { @@ -568,7 +569,8 @@ class _WorkingDevice extends Fake implements Device { Future dispose() async {} @override - Future get targetPlatform => Future.value(TargetPlatform.android); + Future get targetPlatform => + Future.value(const TargetPlatform(.android, .unknown)); @override Future stopApp(ApplicationPackage? app, {String? userIdentifier}) async => true; diff --git a/packages/flutter_tools/test/general.shard/hot_shared.dart b/packages/flutter_tools/test/general.shard/hot_shared.dart index 76a84984fbec7..2eb84d4bc8375 100644 --- a/packages/flutter_tools/test/general.shard/hot_shared.dart +++ b/packages/flutter_tools/test/general.shard/hot_shared.dart @@ -42,7 +42,7 @@ class FakeDevFs extends Fake implements DevFS { } class FakeDevice extends Fake implements Device { - FakeDevice({TargetPlatform targetPlatform = TargetPlatform.tester}) + FakeDevice({TargetPlatform targetPlatform = const TargetPlatform(.tester, .unknown)}) : _targetPlatform = targetPlatform; final TargetPlatform _targetPlatform; @@ -148,7 +148,7 @@ class TestFlutterDevice extends FlutterDevice { required ResidentCompiler generator, }) : super( device, - targetPlatform: TargetPlatform.unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler(), diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index fb1c6735b71ee..92c9a79acd2a2 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -250,7 +250,7 @@ name: my_app final devices = [ FlutterDevice( device, - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), @@ -281,7 +281,7 @@ name: my_app final devices = [ FlutterDevice( device, - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), diff --git a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart index aedfe1ece2777..730bc758a85cb 100644 --- a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart +++ b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart @@ -123,7 +123,7 @@ void main() { isA().having( (Exception e) => e.toString(), 'description', - contains('No application found for TargetPlatform.android_arm'), + contains('No application found for android-arm'), ), ), ); diff --git a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart index 879276cddb958..9f954385aa5ff 100644 --- a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart @@ -93,7 +93,7 @@ void main() { }; final DartHooksResult result = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.android_arm64, + targetPlatform: const TargetPlatform(.android, .arm64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, @@ -104,7 +104,7 @@ void main() { await installCodeAssets( dartHookResult: result, environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.android_arm64, + targetPlatform: const TargetPlatform(.android, .arm64), projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -142,7 +142,7 @@ void main() { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, }, - targetPlatform: TargetPlatform.android_x64, + targetPlatform: const TargetPlatform(.android, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(), @@ -170,7 +170,7 @@ void main() { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, }, - targetPlatform: TargetPlatform.android_arm64, + targetPlatform: const TargetPlatform(.android, .arm64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(packagesWithNativeAssetsResult: ['bar']), diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index e305e9a7749c7..bce7fc681e919 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -39,7 +39,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: TargetPlatform.ios.getName(), + kTargetPlatform: const TargetPlatform(.ios, .arm64).getName(), kIosArchs: 'arm64', kSdkRoot: 'path/to/iPhoneOS.sdk', }, @@ -53,7 +53,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: TargetPlatform.android.getName(), + kTargetPlatform: const TargetPlatform(.android, .unknown).getName(), kAndroidArchs: CpuArch.arm64.androidPlatformName, }, inputs: {}, diff --git a/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart index 1467271824cbf..b3b3ebeecc57e 100644 --- a/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart @@ -58,7 +58,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: TargetPlatform.windows_x64, + targetPlatform: const TargetPlatform(.windows, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -100,7 +100,7 @@ void main() { expect( () async => runFlutterSpecificHooks( environmentDefines: {kBuildMode: buildMode.cliName}, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, buildCodeAssets: const BuildCodeAssetsOptions(appBuildDirectory: null), buildDataAssets: true, diff --git a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart index 4152781956729..169eb2c7df4f1 100644 --- a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart @@ -257,7 +257,7 @@ void main() { }; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, @@ -268,7 +268,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.ios, + targetPlatform: const TargetPlatform(.ios, .arm64), projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, diff --git a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart index 3cfb0a8de6364..d83d1d309946f 100644 --- a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart @@ -59,7 +59,7 @@ void main() { await runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutClang(), @@ -296,10 +296,8 @@ CMAKE_LINKER:FILEPATH=/some/path/to/ld.lld 'cCompilerConfigLinux FileSystemException on resolveSymbolicLinks and throwIfNotFound: false', overrides: { ProcessManager: () => FakeProcessManager.empty(), - FileSystem: () => _ThrowingResolveFileSystem( - fileSystem, - '${environment.outputDir.path}/mock_clang++', - ), + FileSystem: () => + _ThrowingResolveFileSystem(fileSystem, '${environment.outputDir.path}/mock_clang++'), }, () async { if (!const LocalPlatform().isLinux) { @@ -327,10 +325,8 @@ CMAKE_LINKER:FILEPATH=/some/path/to/ld.lld 'cCompilerConfigLinux FileSystemException on resolveSymbolicLinks and throwIfNotFound: true', overrides: { ProcessManager: () => FakeProcessManager.empty(), - FileSystem: () => _ThrowingResolveFileSystem( - fileSystem, - '${environment.outputDir.path}/mock_clang++', - ), + FileSystem: () => + _ThrowingResolveFileSystem(fileSystem, '${environment.outputDir.path}/mock_clang++'), }, () async { if (!const LocalPlatform().isLinux) { diff --git a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart index 13a6c109b5301..001b5786b2722 100644 --- a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart @@ -284,7 +284,7 @@ void main() { } if (flutterTester && !const LocalPlatform().isMacOS) { // The [runFlutterSpecificDartBuild] will - when given - // `TargetPlatform.tester` - enable `flutter test` mode. That means if + // `TargetPlatform(.tester, .unknown)` - enable `flutter test` mode. That means if // this test is run on linux, it's going to do a linux build. // Though this test is mac-specific, so we skip that. // @@ -333,9 +333,9 @@ void main() { kBuildMode: buildMode.cliName, kDarwinArchs: 'arm64 x86_64', }; - final TargetPlatform targetPlatform = flutterTester - ? TargetPlatform.tester - : TargetPlatform.darwin; + final targetPlatform = flutterTester + ? const TargetPlatform(.tester, .unknown) + : const TargetPlatform(.macos, .x64); final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, diff --git a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart index 6926e7ece5988..3011dcff04625 100644 --- a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart @@ -72,7 +72,7 @@ void main() { ]; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -87,7 +87,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.windows_x64, + targetPlatform: const TargetPlatform(.windows, .x64), projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -111,7 +111,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: TargetPlatform.windows_x64, + targetPlatform: const TargetPlatform(.windows, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -140,7 +140,7 @@ void main() { final environmentDefines = {kBuildMode: BuildMode.debug.cliName}; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.windows_x64, + targetPlatform: const TargetPlatform(.windows, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -154,7 +154,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: TargetPlatform.windows_x64, + targetPlatform: const TargetPlatform(.windows, .x64), projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -178,7 +178,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -217,7 +217,7 @@ void main() { // Release mode means the dart build has linking enabled. kBuildMode: BuildMode.release.cliName, }, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -267,7 +267,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.release.cliName}, - targetPlatform: TargetPlatform.linux_x64, + targetPlatform: const TargetPlatform(.linux, .x64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -338,7 +338,7 @@ void main() { await runFlutterSpecificHooks( environmentDefines: {}, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), projectUri: projectUri, fileSystem: fileSystem, buildRunner: target, @@ -381,7 +381,7 @@ CMAKE_LINKER:FILEPATH=/usr/bin/ld.ldd await runFlutterSpecificHooks( environmentDefines: {kBuildMode: 'release'}, - targetPlatform: TargetPlatform.linux_arm64, + targetPlatform: const TargetPlatform(.linux, .arm64), projectUri: projectUri, fileSystem: fileSystem, buildRunner: target, diff --git a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart index e00f52cb9cbd2..d027aebbb5544 100644 --- a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart @@ -36,14 +36,17 @@ void main() { testUsingContext( 'use the nativeAssetsYamlFile when provided', () => testbed.run(() async { - final device = FakeDevice(targetPlatform: TargetPlatform.darwin, sdkNameAndVersion: 'Macos'); + final device = FakeDevice( + targetPlatform: const TargetPlatform(.macos, .x64), + sdkNameAndVersion: 'Macos', + ); final residentCompiler = FakeResidentCompiler(); final flutterDevice = FakeFlutterDevice() ..testUri = testUri ..vmServiceHost = (() => fakeVmServiceHost) ..device = device ..fakeDevFS = devFS - ..targetPlatform = TargetPlatform.darwin + ..targetPlatform = const TargetPlatform(.macos, .x64) ..generator = residentCompiler; fakeVmServiceHost = FakeVmServiceHost(requests: [listViews, listViews]); diff --git a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart index 0ee3f47fa669a..aca9461f817cf 100644 --- a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart @@ -99,9 +99,9 @@ void main() { : FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), ); final environmentDefines = {kBuildMode: buildMode.cliName}; - final TargetPlatform targetPlatform = flutterTester - ? TargetPlatform.tester - : TargetPlatform.windows_x64; + final targetPlatform = flutterTester + ? const TargetPlatform(.tester, .unknown) + : const TargetPlatform(.windows, .x64); final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, diff --git a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart index 34bbb38f745f3..8afcf5004568f 100644 --- a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart +++ b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart @@ -31,7 +31,7 @@ void main() { ); final linuxApp = PrebuiltLinuxApp(executable: 'foo'); - expect(await device.targetPlatform, TargetPlatform.linux_x64); + expect(await device.targetPlatform, const TargetPlatform(.linux, .x64)); expect(device.name, 'Linux'); expect(await device.installApp(linuxApp), true); expect(await device.uninstallApp(linuxApp), true); @@ -53,7 +53,7 @@ void main() { fileSystem: MemoryFileSystem.test(), operatingSystemUtils: FakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64), ); - expect(await deviceArm64Host.targetPlatform, TargetPlatform.linux_arm64); + expect(await deviceArm64Host.targetPlatform, const TargetPlatform(.linux, .arm64)); }); testWithoutContext('LinuxDevice: no devices listed if platform unsupported', () async { diff --git a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart index 428cc4f4698e1..f814d49f5993c 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart @@ -35,7 +35,7 @@ void main() { ); final package = FakeMacOSApp(); - expect(await device.targetPlatform, TargetPlatform.darwin); + expect(await device.targetPlatform, const TargetPlatform(.macos, .x64)); expect(device.name, 'macOS'); expect(await device.installApp(package), true); expect(await device.uninstallApp(package), true); diff --git a/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart index 55266b38be9c1..7d3843a29debf 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart @@ -122,7 +122,7 @@ void main() { expect(await device.isLocalEmulator, isFalse); expect(device.name, 'Mac Designed for iPad'); expect(device.portForwarder, isNot(isNull)); - expect(await device.targetPlatform, TargetPlatform.darwin); + expect(await device.targetPlatform, const TargetPlatform(.macos, .arm64)); expect(await device.installApp(FakeApplicationPackage()), isTrue); expect(await device.isAppInstalled(FakeApplicationPackage()), isTrue); diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart index fb55e6722298e..6ab59fd1d9b57 100644 --- a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart @@ -1282,7 +1282,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { final String name; @override - Future get targetPlatform async => TargetPlatform.ios; + Future get targetPlatform async => const TargetPlatform(.ios, .arm64); @override Future isSupported() async => true; diff --git a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart index 21aa7c7090044..d9cfae7c62099 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart @@ -177,7 +177,7 @@ class TestFlutterDevice extends FlutterDevice { : _vmServiceUris = vmServiceUris, super( generator: FakeResidentCompiler(), - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), ); @@ -220,7 +220,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler(); @override - TargetPlatform targetPlatform = TargetPlatform.android; + TargetPlatform targetPlatform = const TargetPlatform(.android, .unknown); @override Stream get vmServiceUris => Stream.value(testUri); @@ -310,7 +310,7 @@ class FakeDelegateFlutterDevice extends FlutterDevice { ResidentCompiler residentCompiler, this.fakeDevFS, ) : super( - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), buildInfo: buildInfo, generator: residentCompiler, developmentShaderCompiler: const FakeShaderCompiler(), @@ -389,7 +389,7 @@ class FakeProjectFileInvalidator extends Fake implements ProjectFileInvalidator class FakeDevice extends Fake implements Device { FakeDevice({ String sdkNameAndVersion = 'Android', - TargetPlatform targetPlatform = TargetPlatform.android_arm, + TargetPlatform targetPlatform = const TargetPlatform(.android, .armv7), bool isLocalEmulator = false, this.supportsHotRestart = true, this.supportsScreenshot = true, @@ -416,8 +416,9 @@ class FakeDevice extends Fake implements Device { bool supportsFlutterExit; @override - PlatformType get platformType => - _targetPlatform == TargetPlatform.web_javascript ? PlatformType.web : PlatformType.android; + PlatformType get platformType => _targetPlatform == const TargetPlatform(.web, .unknown) + ? PlatformType.web + : PlatformType.android; @override Future get sdkNameAndVersion async => _sdkNameAndVersion; diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 1564f472becb1..17458c71fea74 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -240,7 +240,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: TargetPlatform.android_arm.getName(), + targetPlatform: const TargetPlatform(.android, .armv7).getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -308,7 +308,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'reload-barred', - targetPlatform: TargetPlatform.android_arm.getName(), + targetPlatform: const TargetPlatform(.android, .armv7).getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -361,7 +361,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: TargetPlatform.android_arm.getName(), + targetPlatform: const TargetPlatform(.android, .armv7).getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -579,7 +579,7 @@ void main() { final Event event = fakeAnalytics.sentEvents.first; expect(event.eventName.label, 'hot_runner_info'); expect(event.eventData['label'], 'reload'); - expect(event.eventData['targetPlatform'], TargetPlatform.android_arm.getName()); + expect(event.eventData['targetPlatform'], const TargetPlatform(.android, .armv7).getName()); }), ); @@ -725,7 +725,10 @@ void main() { expect(hotRunnerInfoEvents, hasLength(1)); final Event newEvent = hotRunnerInfoEvents.first; expect(newEvent.eventData['label'], 'restart'); - expect(newEvent.eventData['targetPlatform'], TargetPlatform.android_arm.getName()); + expect( + newEvent.eventData['targetPlatform'], + const TargetPlatform(.android, .armv7).getName(), + ); }), ); @@ -943,7 +946,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: TargetPlatform.android_arm.getName(), + targetPlatform: const TargetPlatform(.android, .armv7).getName(), sdkName: 'Android', emulator: false, fullRestart: true, @@ -1310,7 +1313,7 @@ flutter: 'ResidentRunner printHelpDetails hides v on web in profile mode', () => testbed.run(() async { final FlutterDevice flutterDevice = await FlutterDevice.create( - FakeDevice(targetPlatform: TargetPlatform.web_javascript), + FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)), target: 'lib/main.dart', buildInfo: BuildInfo.profile, platform: FakePlatform(), @@ -1685,7 +1688,7 @@ flutter: 'FlutterDevice uses dartdevc configuration when targeting web', () async { fakeVmServiceHost = FakeVmServiceHost(requests: []); - final device = FakeDevice(targetPlatform: TargetPlatform.web_javascript); + final device = FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)); final residentCompiler = (await FlutterDevice.create( device, @@ -1736,7 +1739,7 @@ flutter: 'FlutterDevice uses dartdevc configuration when targeting web with null-safety autodetected', () async { fakeVmServiceHost = FakeVmServiceHost(requests: []); - final device = FakeDevice(targetPlatform: TargetPlatform.web_javascript); + final device = FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)); final residentCompiler = (await FlutterDevice.create( @@ -2121,7 +2124,7 @@ flutter: final webFlutterDevice = FakeFlutterDevice() ..vmServiceHost = (() => fakeVmServiceHost) ..fakeDevFS = devFS - ..targetPlatform = TargetPlatform.web_javascript; + ..targetPlatform = const TargetPlatform(.web, .unknown); fakeVmServiceHost = FakeVmServiceHost( requests: [ listViews, @@ -2234,14 +2237,17 @@ flutter: testUsingContext( 'use the nativeAssetsYamlFile when provided', () => testbed.run(() async { - final device = FakeDevice(targetPlatform: TargetPlatform.darwin, sdkNameAndVersion: 'Macos'); + final device = FakeDevice( + targetPlatform: const TargetPlatform(.macos, .x64), + sdkNameAndVersion: 'Macos', + ); final residentCompiler = FakeResidentCompiler(); final flutterDevice = FakeFlutterDevice() ..testUri = testUri ..vmServiceHost = (() => fakeVmServiceHost) ..device = device ..fakeDevFS = devFS - ..targetPlatform = TargetPlatform.darwin + ..targetPlatform = const TargetPlatform(.macos, .x64) ..generator = residentCompiler; fakeVmServiceHost = FakeVmServiceHost(requests: [listViews, listViews]); @@ -2329,7 +2335,7 @@ flutter: testUsingContext( 'correctly caches Web Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = TargetPlatform.web_javascript; + flutterDevice.targetPlatform = const TargetPlatform(.web, .unknown); residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( @@ -2349,7 +2355,7 @@ flutter: testUsingContext( 'correctly caches Fuchsia Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = TargetPlatform.fuchsia_arm64; + flutterDevice.targetPlatform = const TargetPlatform(.fuchsia, .arm64); residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( @@ -2369,7 +2375,7 @@ flutter: testUsingContext( 'correctly caches Android Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = TargetPlatform.android_arm; + flutterDevice.targetPlatform = const TargetPlatform(.android, .armv7); residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 754492f7ae180..9843e5d69b73b 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -2508,7 +2508,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { Exception? reportError; @override - TargetPlatform get targetPlatform => TargetPlatform.web_javascript; + TargetPlatform get targetPlatform => const TargetPlatform(.web, .unknown); @override ResidentCompiler? generator; diff --git a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart index 0d2919b3e51de..399774e9decd9 100644 --- a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart @@ -3110,7 +3110,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.attached, this.platformType = PlatformType.android, - TargetPlatform deviceTargetPlatform = TargetPlatform.android, + TargetPlatform deviceTargetPlatform = const TargetPlatform(.android, .unknown), }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3126,7 +3126,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.wireless, this.platformType = PlatformType.android, - TargetPlatform deviceTargetPlatform = TargetPlatform.android, + TargetPlatform deviceTargetPlatform = const TargetPlatform(.android, .unknown), }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3142,7 +3142,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.attached, this.platformType = PlatformType.fuchsia, - TargetPlatform deviceTargetPlatform = TargetPlatform.fuchsia_arm64, + TargetPlatform deviceTargetPlatform = const TargetPlatform(.fuchsia, .arm64), }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3204,7 +3204,7 @@ class FakeDevice extends Fake implements Device { Category? get category => Category.mobile; @override - Future get targetPlatformDisplayName async => (await targetPlatform).getName(); + Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; } class FakeIOSDevice extends Fake implements IOSDevice { @@ -3314,7 +3314,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { Future get targetPlatformDisplayName async => 'ios'; @override - Future get targetPlatform async => TargetPlatform.tester; + Future get targetPlatform async => const TargetPlatform(.tester, .unknown); } class FakeTerminal extends Fake implements AnsiTerminal { diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart index addc8780ed7fb..dd5af7dcbd3d7 100644 --- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart @@ -1293,7 +1293,7 @@ void main() { final residentRunner = FakeResidentRunner( FlutterDevice( FakeDevice(), - targetPlatform: .unsupported, + targetPlatform: const TargetPlatform(.unsupported, .unknown), buildInfo: BuildInfo.debug, generator: FakeResidentCompiler(), developmentShaderCompiler: const FakeShaderCompiler(), @@ -1674,7 +1674,9 @@ TerminalHandler setUpTerminalHandler( ), generator: FakeResidentCompiler(), developmentShaderCompiler: const FakeShaderCompiler(), - targetPlatform: web ? TargetPlatform.web_javascript : TargetPlatform.android_arm, + targetPlatform: web + ? const TargetPlatform(.web, .unknown) + : const TargetPlatform(.android, .armv7), ); device.vmService = nullVmService ? null : FakeVmServiceHost(requests: requests).vmService; final residentRunner = FakeResidentRunner(device, testLogger, localFileSystem) diff --git a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart index 5b23d99259417..b8574203056a9 100644 --- a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart @@ -37,8 +37,8 @@ void main() { final processManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', + 'Artifact.engineDartAotRuntime.web-javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', '--sdk-root', 'HostArtifact.flutterWebSdk/', '--incremental', @@ -118,7 +118,7 @@ void main() { final processManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'Artifact.engineDartBinary.TargetPlatform.web_javascript', + 'Artifact.engineDartBinary.web-javascript', 'compile', 'wasm', '--packages=.dart_tool/package_config.json', diff --git a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart index b03042970f904..ec32edfd65e3f 100644 --- a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart +++ b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart @@ -111,7 +111,7 @@ void main() { expect(await device.isLocalEmulator, isFalse); expect(device.name, 'Flutter test device'); expect(device.portForwarder, isNot(isNull)); - expect(await device.targetPlatform, TargetPlatform.tester); + expect(await device.targetPlatform, const TargetPlatform(.tester, .unknown)); expect(await device.installApp(FakeApplicationPackage()), isTrue); expect(await device.isAppInstalled(FakeApplicationPackage()), isFalse); diff --git a/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart b/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart index dd740d4a997a6..38c8556baf2c6 100644 --- a/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart @@ -72,7 +72,7 @@ void main() { FakeCommand cmakeGenerationCommand({ String? flavor, - TargetPlatform targetPlatform = TargetPlatform.windows_x64, + TargetPlatform targetPlatform = const TargetPlatform(.windows, .x64), }) { final String buildDir = flavor != null && flavor.isNotEmpty ? r'C:\build\windows\x64\' + flavor @@ -93,10 +93,7 @@ void main() { ); } - FakeCommand buildCommand( - String buildMode, { - String? flavor, - }) { + FakeCommand buildCommand(String buildMode, {String? flavor}) { final String buildDir = flavor != null && flavor.isNotEmpty ? r'C:\build\windows\x64\' + flavor : r'C:\build\windows\x64'; @@ -137,7 +134,7 @@ void main() { 'returns legacy path when no flavor', () { expect( - getWindowsBuildDirectory(TargetPlatform.windows_x64), + getWindowsBuildDirectory(const TargetPlatform(.windows, .x64)), endsWith(fileSystem.path.join('windows', 'x64')), ); }, @@ -151,7 +148,7 @@ void main() { 'inserts flavor segment when flavor is set', () { expect( - getWindowsBuildDirectory(TargetPlatform.windows_x64, 'apple'), + getWindowsBuildDirectory(const TargetPlatform(.windows, .x64), 'apple'), endsWith(fileSystem.path.join('windows', 'x64', 'apple')), ); }, diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart index 45558a038c23c..e089d1bd9f6c6 100644 --- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart @@ -24,7 +24,7 @@ void main() { final File dummyFile = MemoryFileSystem.test().file('dummy'); final windowsApp = PrebuiltWindowsApp(executable: 'foo', applicationPackage: dummyFile); - expect(await windowsDevice.targetPlatform, TargetPlatform.windows_x64); + expect(await windowsDevice.targetPlatform, const TargetPlatform(.windows, .x64)); expect(windowsDevice.name, 'Windows'); expect(await windowsDevice.installApp(windowsApp), true); expect(await windowsDevice.uninstallApp(windowsApp), true); diff --git a/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart b/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart index 8028f93f857b5..2d8960fb31c67 100644 --- a/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart +++ b/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart @@ -32,9 +32,9 @@ void main() { outputPath: tmpDir.childFile('test_shader.frag.out').path, targetPlatform: targetSkslOnly // web_javascript compiles to sksl only. - ? TargetPlatform.web_javascript + ? const TargetPlatform(.web, .unknown) // tester compiles to sksl and runtime-stage-vulkan - : TargetPlatform.tester, + : const TargetPlatform(.tester, .unknown), ); } @@ -66,7 +66,7 @@ void main() { final bool compileResult = await shaderCompiler.compileShader( input: globals.fs.file(inkSparklePath), outputPath: inkSparkleOutputPath, - targetPlatform: TargetPlatform.tester, + targetPlatform: const TargetPlatform(.tester, .unknown), ); final File resultFile = globals.fs.file(inkSparkleOutputPath); diff --git a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart index 13cf6728c1a1b..aeaed75728386 100644 --- a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart +++ b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart @@ -372,7 +372,7 @@ Future _analyzeEntity(FileSystemEntity target) async { final ProcessResult exec = await Process.run( globals.artifacts!.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), args, workingDirectory: target is Directory ? target.path : target.dirname, @@ -400,7 +400,7 @@ Future _runFlutterSnapshot(List flutterCommandArgs, Directory work final args = [ globals.artifacts!.getArtifactPath( Artifact.engineDartBinary, - platform: TargetPlatform.web_javascript, + platform: const TargetPlatform(.web, .unknown), ), flutterToolsSnapshotPath, ...flutterCommandArgs, diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index 4745aa6b32fb5..8a264f8914097 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -37,7 +37,7 @@ List fakeDevices = [ ), FakeDeviceJsonData( FakeDevice('webby', 'webby') - ..targetPlatform = Future.value(TargetPlatform.web_javascript) + ..targetPlatform = Future.value(const TargetPlatform(.web, .unknown)) ..cpuArch = Future.value(CpuArch.unknown) ..sdkNameAndVersion = Future.value('Web SDK (1.2.4)'), { @@ -90,7 +90,7 @@ List fakeDevices = [ type: PlatformType.ios, connectionInterface: DeviceConnectionInterface.wireless, ) - ..targetPlatform = Future.value(TargetPlatform.ios) + ..targetPlatform = Future.value(const TargetPlatform(.ios, .arm64)) ..cpuArch = Future.value(CpuArch.arm64) ..sdkNameAndVersion = Future.value('iOS 16'), { @@ -167,7 +167,9 @@ class FakeDevice extends Device { Future dispose() async {} @override - Future targetPlatform = Future.value(TargetPlatform.android_arm); + Future targetPlatform = Future.value( + const TargetPlatform(.android, .armv7), + ); @override Future cpuArch = Future.value(CpuArch.armv7); From 9c98b2622b4dcc656c1d822a6c2497cfd6c316f4 Mon Sep 17 00:00:00 2001 From: Ram-Dawson <135011905+Ram-Dawson@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:43:03 +0800 Subject: [PATCH 100/147] fix(tool): parse ADB device lists by connection state (#189973) Fixes #189972 Related to #189430 and #189369. This supersedes the narrower parser approach from closed PR #189431. ## Summary ADB formats long device-list rows as [`%-22s %s`](https://android.googlesource.com/platform/packages/modules/adb/+/1cf2f017d312f73b3dc53bda85ef2610e35a80e9/transport.cpp#1410): the serial field has a minimum width, so a long wireless serial can be separated from its connection state by a single space. mDNS conflict suffixes can also introduce whitespace inside the serial. [PR #189369](https://github.com/flutter/flutter/pull/189369) correctly protects serials containing whitespace by requiring two spaces or a tab before the state. [PR #189431](https://github.com/flutter/flutter/pull/189431) proposed a narrower one-space fallback for serials without whitespace and was closed after #189369 landed. The combined case has both an mDNS conflict suffix and a valid single-space state delimiter, so neither earlier rule accepts it. This change parses the explicit known ADB connection-state field instead of inferring a boundary from whitespace width. The serial capture is greedy so state-like words and mDNS conflict suffixes remain part of the serial, and right-side padding from ADB's minimum-width field is removed. ## Tests Added regression coverage for: - ordinary single-space wireless mDNS serials; - mDNS conflict serials containing ` (2)` with `device` and `offline` states; - every state currently recognized by Flutter Tools; - serials that contain state-like words; and - existing padded and tab-separated output forms. Ran: ```text flutter test packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart flutter analyze packages/flutter_tools ``` The device-discovery test file passed with 16 tests, and analysis reported no issues. ## Real-device validation After deleting `bin/cache/flutter_tools.snapshot`, unmodified `upstream/master` at `0ed149e63db1f68d2042063baf0a99d969ccd84b` omitted the wireless Android device and reported an unexpected ADB parsing failure. With this patch, `flutter devices -v` listed the device with the complete mDNS conflict serial and successfully used that serial for `adb -s ... shell getprop`. ## Scope This PR changes only Flutter Tools parsing and its Android device-discovery tests. It does not change ADB, mDNS pairing, wireless transport behavior, or device-state handling after a row has been parsed. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../src/android/android_device_discovery.dart | 17 ++-- .../android_device_discovery_test.dart | 78 +++++++++++++++++++ 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_device_discovery.dart b/packages/flutter_tools/lib/src/android/android_device_discovery.dart index 0caa60183534d..f292cdbb33189 100644 --- a/packages/flutter_tools/lib/src/android/android_device_discovery.dart +++ b/packages/flutter_tools/lib/src/android/android_device_discovery.dart @@ -113,18 +113,17 @@ class AndroidDevices extends PollingDeviceDiscovery { // Parses the output of `adb devices -l`. // // The regex is structured as: - // 1. Group 1 (Serial): Lazily matched to allow spaces in the serial (which - // can happen during wireless ADB mDNS name conflicts, e.g., "device (2)"). - // The column separator requires at least two spaces or a tab to prevent - // single spaces within a serial from being mis-matched. + // 1. Group 1 (Serial): Greedily matched before the state. ADB formats long + // listings as `%-22s %s`, so the width is a minimum: serials longer than + // 22 characters are followed by one space. Serials can also contain + // whitespace, such as a wireless mDNS conflict suffix (`device (2)`). // 2. Group 2 (State): Matches known ADB device states explicitly, including // "no permissions" (which contains a space). Explicitly listing states - // prevents false positive state matching on extra device info/attributes - // or serial name components. + // lets the greedy serial capture retain state-like serial components. // 3. Group 3 (Extra Info): Optional trailing details (e.g. key:value pairs // like "product:mokey model:mokey device:mokey transport_id:1" or "usb:123"). static final _kDeviceRegex = RegExp( - r'^(.*?)(?:\s{2,}|\t+)' + r'^(.*)\s+' r'(device|offline|unauthorized|no permissions|bootloader|recovery|sideload|rescue|connecting|authorizing|host|unknown)' r'(?:\s+(.*)|$)', ); @@ -163,7 +162,9 @@ class AndroidDevices extends PollingDeviceDiscovery { if (_kDeviceRegex.hasMatch(line)) { final Match match = _kDeviceRegex.firstMatch(line)!; - final String deviceID = match[1]!; + // The greedy serial capture includes the optional padding from ADB's + // minimum-width serial field. + final String deviceID = match[1]!.trimRight(); final String deviceState = match[2]!; String? rest = match[3]; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart index 158798dc6c65e..fe7fc8ee03ad1 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_discovery_test.dart @@ -238,6 +238,84 @@ adb-ZY22MGW35T-Z3uXXq (2)._adb-tls-connect._tcp device product:vantage_ge mod 'expectedId': '127.0.0.1:5555', 'expectedStatus': 'success', }, + // ADB long listings use a minimum-width serial column, so a serial that + // is 22 characters or longer can be followed by only one space. + { + 'input': + 'adb-0123456789abcdef._adb-tls-connect._tcp device product:socrates model:22127RK46C device:socrates transport_id:1', + 'expectedId': 'adb-0123456789abcdef._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + // mDNS conflict suffixes introduce whitespace into the serial itself. + { + 'input': + 'adb-0123456789abcdef (2)._adb-tls-connect._tcp device product:socrates model:22127RK46C device:socrates transport_id:1', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp offline', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'offline', + }, + // Serial contents may themselves include known ADB state names. + { + 'input': 'my device offline device', + 'expectedId': 'my device offline', + 'expectedStatus': 'success', + }, + // Exercise every state currently recognized by Flutter with the ADB + // single-space form and a serial containing whitespace. + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp unauthorized', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'unauthorized', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp no permissions', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'no permissions', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp bootloader', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp recovery', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp sideload', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp rescue', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp connecting', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp authorizing', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp host', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, + { + 'input': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp unknown', + 'expectedId': 'adb-0123456789abcdef (2)._adb-tls-connect._tcp', + 'expectedStatus': 'success', + }, // States that should go to diagnostics { 'input': '015d172c98400a03 offline', From 484df22fe9b898277598b8ef42e20b54db1f023f Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Wed, 29 Jul 2026 18:47:07 +0200 Subject: [PATCH 101/147] [test cross imports] flutter_test cross import fixes (#189265) This PR fixes some more tests in flutter_test in relation to importing Material The `finders_test` was cleaned up a lot, but the remainder in that file is related to Material Tooltip, which is still a point of discussion relating to its test Finders. Part of https://github.com/flutter/flutter/issues/177415 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- dev/bots/check_tests_cross_imports.dart | 9 - .../flutter_test/test/all_elements_test.dart | 2 +- packages/flutter_test/test/finders_test.dart | 236 +++++++++++++----- .../flutter_test/test/live_binding_test.dart | 39 +-- .../test/live_widget_controller_test.dart | 12 +- .../flutter_test/test/mock_canvas_test.dart | 33 +-- .../flutter_test/test/navigator_test.dart | 226 +++++++++++------ .../test/semantics_finder_test.dart | 2 +- .../test/test_text_input_test.dart | 23 +- .../test/utils/memory_leak_tests.dart | 3 +- .../test/widget_tester_live_device_test.dart | 2 +- .../flutter_test/animation_sheet_builder.dart | 2 +- .../animation_sheet_builder.dart.expect | 2 +- .../test_fixes/flutter_test/matchers.dart | 2 +- .../flutter_test/matchers.dart.expect | 2 +- 15 files changed, 351 insertions(+), 244 deletions(-) diff --git a/dev/bots/check_tests_cross_imports.dart b/dev/bots/check_tests_cross_imports.dart index d0bf9440f8cf1..b51c2c6a69425 100644 --- a/dev/bots/check_tests_cross_imports.dart +++ b/dev/bots/check_tests_cross_imports.dart @@ -187,17 +187,8 @@ class TestsCrossImportChecker { 'packages/flutter_test/lib/src/widget_tester.dart', 'packages/flutter_test/lib/src/finders.dart', 'packages/flutter_test/lib/src/matchers.dart', - 'packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart', - 'packages/flutter_test/test_fixes/flutter_test/matchers.dart', - 'packages/flutter_test/test/navigator_test.dart', - 'packages/flutter_test/test/mock_canvas_test.dart', - 'packages/flutter_test/test/semantics_finder_test.dart', 'packages/flutter_test/test/accessibility_window_test.dart', - 'packages/flutter_test/test/widget_tester_live_device_test.dart', - 'packages/flutter_test/test/all_elements_test.dart', - 'packages/flutter_test/test/utils/memory_leak_tests.dart', 'packages/flutter_test/test/widget_tester_test.dart', - 'packages/flutter_test/test/live_widget_controller_test.dart', 'packages/flutter_test/test/accessibility_test.dart', 'packages/flutter_test/test/finders_test.dart', 'packages/flutter_test/test/controller_test.dart', diff --git a/packages/flutter_test/test/all_elements_test.dart b/packages/flutter_test/test/all_elements_test.dart index 8f43efd2d38d4..11569a93dc4f7 100644 --- a/packages/flutter_test/test/all_elements_test.dart +++ b/packages/flutter_test/test/all_elements_test.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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter_test/test/finders_test.dart b/packages/flutter_test/test/finders_test.dart index 311a3cd257b50..34953a6a6fd6b 100644 --- a/packages/flutter_test/test/finders_test.dart +++ b/packages/flutter_test/test/finders_test.dart @@ -8,6 +8,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +const _kBlue = Color(0xFF0000FF); + const List fooBarTexts = [ Text('foo', textDirection: TextDirection.ltr), Text('bar', textDirection: TextDirection.ltr), @@ -24,9 +26,9 @@ void main() { testWidgets('finds Button widgets with Image', (WidgetTester tester) async { addTearDown(imageCache.clear); await tester.pumpWidget( - _boilerplate(ElevatedButton(onPressed: null, child: Image(image: FileImage(File('test'))))), + _boilerplate(TestButton(child: Image(image: FileImage(File('test'))))), ); - expect(find.widgetWithImage(ElevatedButton, FileImage(File('test'))), findsOneWidget); + expect(find.widgetWithImage(TestButton, FileImage(File('test'))), findsOneWidget); }); }); @@ -170,9 +172,19 @@ void main() { testWidgets('finds EditableText widgets', (WidgetTester tester) async { final controller = TextEditingController()..text = 'this is test'; addTearDown(controller.dispose); + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); await tester.pumpWidget( - MaterialApp( - home: Scaffold(body: _boilerplate(TextField(controller: controller))), + TestWidgetsApp( + home: _boilerplate( + EditableText( + controller: controller, + focusNode: focusNode, + style: const TextStyle(), + cursorColor: const Color(0xFF000000), + backgroundCursorColor: _kBlue, + ), + ), ), ); @@ -276,7 +288,7 @@ void main() { Semantics( label: 'Add', button: true, - child: const TextButton(onPressed: null, child: Text('+')), + child: const TestButton(child: Text('+')), ), ), ); @@ -308,22 +320,20 @@ void main() { semanticsHandle.dispose(); }); - testWidgets( - 'Throws StateError if semantics are not enabled (bySemanticsIdentifier)', - (WidgetTester tester) async { - expect( - () => find.bySemanticsIdentifier('Add'), - throwsA( - isA().having( - (StateError e) => e.message, - 'message', - contains('Semantics are not enabled'), - ), + testWidgets('Throws StateError if semantics are not enabled (bySemanticsIdentifier)', ( + WidgetTester tester, + ) async { + expect( + () => find.bySemanticsIdentifier('Add'), + throwsA( + isA().having( + (StateError e) => e.message, + 'message', + contains('Semantics are not enabled'), ), - ); - }, - semanticsEnabled: false, - ); + ), + ); + }, semanticsEnabled: false); testWidgets('finds Semantically labeled widgets by identifier', (WidgetTester tester) async { final SemanticsHandle semanticsHandle = tester.ensureSemantics(); @@ -332,7 +342,7 @@ void main() { Semantics( identifier: 'Add', button: true, - child: const TextButton(onPressed: null, child: Text('+')), + child: const TestButton(child: Text('+')), ), ), ); @@ -825,7 +835,7 @@ void main() { (WidgetTester tester) async { var tapCount = 0; await tester.pumpWidget( - MaterialApp( + TestWidgetsApp( home: ListView( children: [ const SizedBox(height: 2000), // Push the target off-screen @@ -854,27 +864,23 @@ void main() { testWidgets('tapping directly on a Sliver produces an error', (WidgetTester tester) async { var sliverToBoxAdapterTapped = 0; await tester.pumpWidget( - MaterialApp( - title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), - home: Scaffold( - body: SafeArea( - child: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: GestureDetector( - onTap: () { - sliverToBoxAdapterTapped++; - }, - child: Container( - color: Colors.orange, - padding: const EdgeInsets.all(16.0), - child: const Text('Sliver Grid Header', style: TextStyle(fontSize: 28)), - ), + TestWidgetsApp( + home: SafeArea( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: GestureDetector( + onTap: () { + sliverToBoxAdapterTapped++; + }, + child: Container( + color: _kBlue, + padding: const EdgeInsets.all(16.0), + child: const Text('Sliver Grid Header', style: TextStyle(fontSize: 28)), ), ), - ], - ), + ), + ], ), ), ), @@ -898,27 +904,23 @@ void main() { ) async { var sliverToBoxAdapterTapped = 0; await tester.pumpWidget( - MaterialApp( - title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), - home: Scaffold( - body: SafeArea( - child: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: GestureDetector( - onTap: () { - sliverToBoxAdapterTapped++; - }, - child: Container( - color: Colors.orange, - padding: const EdgeInsets.all(16.0), - child: const Text('Sliver Grid Header', style: TextStyle(fontSize: 28)), - ), + TestWidgetsApp( + home: SafeArea( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: GestureDetector( + onTap: () { + sliverToBoxAdapterTapped++; + }, + child: Container( + color: _kBlue, + padding: const EdgeInsets.all(16.0), + child: const Text('Sliver Grid Header', style: TextStyle(fontSize: 28)), ), ), - ], - ), + ), + ], ), ), ), @@ -1770,7 +1772,7 @@ void main() { final controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget( - MaterialApp( + TestWidgetsApp( home: SingleChildScrollView( controller: controller, child: const SizedBox(width: 100, height: 1000), @@ -1788,7 +1790,7 @@ void main() { final controller = ScrollController(initialScrollOffset: 400); addTearDown(controller.dispose); await tester.pumpWidget( - MaterialApp( + TestWidgetsApp( home: SingleChildScrollView( controller: controller, child: const SizedBox(width: 100, height: 1000), @@ -1806,7 +1808,7 @@ void main() { final controller = ScrollController(); addTearDown(controller.dispose); await tester.pumpWidget( - MaterialApp( + TestWidgetsApp( home: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: controller, @@ -1825,7 +1827,7 @@ void main() { final controller = ScrollController(initialScrollOffset: 200); addTearDown(controller.dispose); await tester.pumpWidget( - MaterialApp( + TestWidgetsApp( home: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: controller, @@ -1844,7 +1846,7 @@ void main() { WidgetTester tester, ) async { await tester.pumpWidget( - const MaterialApp( + const TestWidgetsApp( home: Column( children: [ SingleChildScrollView( @@ -1862,7 +1864,7 @@ void main() { testWidgets('can exclusively find node that scrolls vertically', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( + const TestWidgetsApp( home: Column( children: [ SingleChildScrollView( @@ -2062,7 +2064,7 @@ Widget _boilerplate(Widget child) { textDirection: TextDirection.ltr, child: Navigator( onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute(builder: (BuildContext context) => child); + return TestRoute(builder: (BuildContext context) => child); }, ), ); @@ -2119,6 +2121,106 @@ Widget _deepWidgetTree({required int depth, required Widget child}) { return tree; } +class TestRoute extends PageRoute { + TestRoute({ + this.child, + this.builder, + RouteSettings super.settings = const RouteSettings(), + this.barrierColor, + this.maintainState = false, + this.transitionDuration = Duration.zero, + this.reverseTransitionDuration = Duration.zero, + this.transitionsBuilder, + super.fullscreenDialog, + super.allowSnapshotting, + }) : assert(child != null || builder != null, 'Either child or builder must be provided.'); + + final Widget? child; + final WidgetBuilder? builder; + final PageTransitionsBuilder? transitionsBuilder; + + @override + final Duration transitionDuration; + + @override + final Duration reverseTransitionDuration; + + @override + final Color? barrierColor; + + @override + String? get barrierLabel => null; + + @override + final bool maintainState; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return child ?? builder?.call(context) ?? const SizedBox.shrink(); + } + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + if (transitionsBuilder == null) { + return child; + } + + return transitionsBuilder!.buildTransitions( + this, + context, + animation, + secondaryAnimation, + child, + ); + } +} + +class TestButton extends StatelessWidget { + const TestButton({ + required this.child, + this.focusNode, + this.autofocus = false, + this.onPressed, + this.behavior, + super.key, + }); + + final bool autofocus; + final FocusNode? focusNode; + final VoidCallback? onPressed; + final Widget child; + final HitTestBehavior? behavior; + + void _onFocus() => focusNode?.requestFocus(); + + @override + Widget build(BuildContext context) { + return Semantics( + label: 'button', + button: true, + enabled: onPressed != null, + onTap: onPressed, + onFocus: _onFocus, + focusable: true, + child: FocusableActionDetector( + enabled: onPressed != null, + focusNode: focusNode, + autofocus: autofocus, + child: GestureDetector(behavior: behavior, onTap: onPressed, child: child), + ), + ); + } +} + class _FakeFinder extends FinderBase { _FakeFinder({ this.allCandidatesCallback, diff --git a/packages/flutter_test/test/live_binding_test.dart b/packages/flutter_test/test/live_binding_test.dart index 6f4f58863a57e..71a6deb39f726 100644 --- a/packages/flutter_test/test/live_binding_test.dart +++ b/packages/flutter_test/test/live_binding_test.dart @@ -9,36 +9,9 @@ import 'package:flutter_test/flutter_test.dart'; // This file is for testings that require a `LiveTestWidgetsFlutterBinding` void main() { - PageRoute defaultPageRouteBuilder(RouteSettings settings, WidgetBuilder builder) { - return PageRouteBuilder( - settings: settings, - pageBuilder: - ( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - ) => builder(context), - transitionsBuilder: - ( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) => child, - ); - } - - Widget buildTestApp({required Widget child}) { - return WidgetsApp( - color: const Color(0xFFFFFFFF), - pageRouteBuilder: defaultPageRouteBuilder, - home: SizedBox.expand(child: Center(child: child)), - ); - } - final binding = LiveTestWidgetsFlutterBinding(); testWidgets('Input PointerAddedEvent', (WidgetTester tester) async { - await tester.pumpWidget(buildTestApp(child: const Text('Test'))); + await tester.pumpWidget(const TestWidgetsApp(home: Text('Test'))); await tester.pump(); final TestGesture gesture = await tester.createGesture(); // This mimics the start of a gesture as seen on a device, where inputs @@ -50,8 +23,8 @@ void main() { testWidgets('Input PointerHoverEvent', (WidgetTester tester) async { PointerHoverEvent? hoverEvent; await tester.pumpWidget( - buildTestApp( - child: MouseRegion( + TestWidgetsApp( + home: MouseRegion( child: const Text('Test'), onHover: (PointerHoverEvent event) { hoverEvent = event; @@ -71,8 +44,8 @@ void main() { testWidgets('hitTesting works when using setSurfaceSize', (WidgetTester tester) async { var invocations = 0; await tester.pumpWidget( - buildTestApp( - child: GestureDetector( + TestWidgetsApp( + home: GestureDetector( onTap: () { invocations++; }, @@ -100,7 +73,7 @@ void main() { testWidgets('setSurfaceSize works', (WidgetTester tester) async { addTearDown(binding.resetLayers); - await tester.pumpWidget(buildTestApp(child: const Text('Test'))); + await tester.pumpWidget(const TestWidgetsApp(home: Center(child: Text('Test')))); final Size windowCenter = tester.view.physicalSize / tester.view.devicePixelRatio / 2; final double windowCenterX = windowCenter.width; diff --git a/packages/flutter_test/test/live_widget_controller_test.dart b/packages/flutter_test/test/live_widget_controller_test.dart index ac354b942d8bd..01bd2048e9a28 100644 --- a/packages/flutter_test/test/live_widget_controller_test.dart +++ b/packages/flutter_test/test/live_widget_controller_test.dart @@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; // This test is very fragile and bypasses some zone-related checks. @@ -47,9 +47,9 @@ class _CountButtonState extends State { int counter = 0; @override Widget build(BuildContext context) { - return ElevatedButton( + return GestureDetector( child: Text('Counter $counter'), - onPressed: () { + onTap: () { setState(() { counter += 1; }); @@ -93,7 +93,7 @@ void main() { TestBinding.ensureInitialized(); test('Test pump on LiveWidgetController', () async { - runApp(const MaterialApp(home: Center(child: CountButton()))); + runApp(const TestWidgetsApp(home: CountButton())); await SchedulerBinding.instance.endOfFrame; final WidgetController controller = LiveWidgetController(WidgetsBinding.instance); @@ -106,7 +106,7 @@ void main() { }); test('Test pumpAndSettle on LiveWidgetController', () async { - runApp(const MaterialApp(home: Center(child: AnimateSample()))); + runApp(const TestWidgetsApp(home: AnimateSample())); await SchedulerBinding.instance.endOfFrame; final WidgetController controller = LiveWidgetController(WidgetsBinding.instance); expect(find.text('Value: 1.0'), findsNothing); @@ -117,7 +117,7 @@ void main() { test('Input event array on LiveWidgetController', () async { final logs = []; runApp( - MaterialApp( + TestWidgetsApp( home: Listener( onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'), onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'), diff --git a/packages/flutter_test/test/mock_canvas_test.dart b/packages/flutter_test/test/mock_canvas_test.dart index 92362c9871eba..4afb3e844b62d 100644 --- a/packages/flutter_test/test/mock_canvas_test.dart +++ b/packages/flutter_test/test/mock_canvas_test.dart @@ -4,9 +4,12 @@ import 'dart:math' as math; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +const _kTransparent = Color(0x00000000); +const _kBlue = Color(0xFF0000FF); + class MyPainter extends CustomPainter { const MyPainter({required this.color}); @@ -55,7 +58,7 @@ void main() { testWidgets('matches when the predicate returns true', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -72,7 +75,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), // The #restore call is never evaluated ]); }); @@ -80,7 +83,7 @@ void main() { testWidgets('fails when the predicate always returns false', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -99,7 +102,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), const MethodAndArguments(#restore, []), ]); }); @@ -107,7 +110,7 @@ void main() { testWidgets('fails when the predicate throws', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -132,7 +135,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), // The #restore call is never evaluated ]); }); @@ -142,7 +145,7 @@ void main() { testWidgets('matches when the predicate always returns true', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -159,7 +162,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), const MethodAndArguments(#restore, []), ]); }); @@ -167,7 +170,7 @@ void main() { testWidgets('fails when the predicate returns false', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -187,7 +190,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), // The #restore call is never evaluated ]); }); @@ -195,7 +198,7 @@ void main() { testWidgets('fails if the predicate ever throws', (WidgetTester tester) async { await tester.pumpWidget( const CustomPaint( - painter: MyPainter(color: Colors.transparent), + painter: MyPainter(color: _kTransparent), child: SizedBox(width: 50, height: 50), ), ); @@ -217,7 +220,7 @@ void main() { expect(methodsAndArguments, [ const MethodAndArguments(#save, []), - const MethodAndArguments(#drawColor, [Colors.transparent, BlendMode.color]), + const MethodAndArguments(#drawColor, [_kTransparent, BlendMode.color]), // The #restore call is never evaluated ]); }); @@ -228,7 +231,7 @@ void main() { const double startAngle = math.pi / 4; const double sweepAngle = math.pi / 2; const useCenter = false; - final paint = Paint()..color = Colors.blue; + final paint = Paint()..color = _kBlue; Future pumpPainter(WidgetTester tester) async { await tester.pumpWidget( @@ -357,7 +360,7 @@ void main() { Offset.zero & const Size.square(50), const Radius.circular(5), ); - final paint = Paint()..color = Colors.blue; + final paint = Paint()..color = _kBlue; Future pumpPainter(WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter_test/test/navigator_test.dart b/packages/flutter_test/test/navigator_test.dart index 63a355e5fd6ec..6040390a387e9 100644 --- a/packages/flutter_test/test/navigator_test.dart +++ b/packages/flutter_test/test/navigator_test.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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -12,106 +12,95 @@ void main() { final observer = TransitionDurationObserver(); await tester.pumpWidget( - MaterialApp( + WidgetsApp( + color: const Color(0xFFFFFFFF), navigatorObservers: [observer], onGenerateRoute: (RouteSettings settings) { return switch (settings.name) { - // A route that uses FadeForwardsPageTransitionsBuilder. '/' => _TestTransitionRoute( - pageTransitionsBuilder: const FadeForwardsPageTransitionsBuilder(), + pageTransitionsBuilder: const _TestSlideUpPageTransitionsBuilder(), builder: (BuildContext context) { - return Scaffold( - body: Center( - child: Column( - children: [ - const Text('Page 1'), - TextButton( - onPressed: () { - Navigator.pushNamed(context, '/2'); - }, - child: const Text('Next'), - ), - ], - ), + return Center( + child: Column( + children: [ + const Text('Page 1'), + GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/2'); + }, + child: const Text('Next'), + ), + ], ), ); }, ), - // A route that uses ZoomPageTransitionsBuilder with custom durations. '/2' => _TestTransitionRoute( - pageTransitionsBuilder: const ZoomPageTransitionsBuilder(), + pageTransitionsBuilder: const _TestSlightRightPageTransitionsBuilder(), transitionDurationOverride: const Duration(milliseconds: 456), reverseTransitionDurationOverride: const Duration(milliseconds: 567), builder: (BuildContext context) { - return Scaffold( - body: Center( - child: Column( - children: [ - const Text('Page 2'), - TextButton( - onPressed: () { - Navigator.pushNamed(context, '/3'); - }, - child: const Text('Next'), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Back'), - ), - ], - ), + return Center( + child: Column( + children: [ + const Text('Page 2'), + GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/3'); + }, + child: const Text('Next'), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Text('Back'), + ), + ], ), ); }, ), - // A route that uses FadeForwardsPageTransitionsBuilder with custom durations. '/3' => _TestTransitionRoute( - pageTransitionsBuilder: const FadeForwardsPageTransitionsBuilder(), + pageTransitionsBuilder: const _TestSlideUpPageTransitionsBuilder(), transitionDurationOverride: const Duration(milliseconds: 678), reverseTransitionDurationOverride: const Duration(milliseconds: 789), builder: (BuildContext context) { - return Scaffold( - body: Center( - child: Column( - children: [ - const Text('Page 3'), - TextButton( - onPressed: () { - Navigator.pushNamed(context, '/4'); - }, - child: const Text('Next'), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Back'), - ), - ], - ), + return Center( + child: Column( + children: [ + const Text('Page 3'), + GestureDetector( + onTap: () { + Navigator.pushNamed(context, '/4'); + }, + child: const Text('Next'), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Text('Back'), + ), + ], ), ); }, ), - // A route that uses ZoomPageTransitionsBuilder. '/4' => _TestTransitionRoute( - pageTransitionsBuilder: const ZoomPageTransitionsBuilder(), + pageTransitionsBuilder: const _TestSlightRightPageTransitionsBuilder(), builder: (BuildContext context) { - return Scaffold( - body: Center( - child: Column( - children: [ - const Text('Page 4'), - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Back'), - ), - ], - ), + return Center( + child: Column( + children: [ + const Text('Page 4'), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Text('Back'), + ), + ], ), ); }, @@ -129,7 +118,7 @@ void main() { expect( observer.transitionDuration, - const FadeForwardsPageTransitionsBuilder().transitionDuration, + const _TestSlideUpPageTransitionsBuilder().transitionDuration, ); await tester.tap(find.text('Next')); @@ -153,7 +142,10 @@ void main() { expect(find.text('Page 4'), findsNothing); await tester.tap(find.text('Next')); - expect(observer.transitionDuration, const ZoomPageTransitionsBuilder().transitionDuration); + expect( + observer.transitionDuration, + const _TestSlightRightPageTransitionsBuilder().transitionDuration, + ); await observer.pumpPastTransition(tester); @@ -165,7 +157,7 @@ void main() { await tester.tap(find.text('Back')); expect( observer.transitionDuration, - const ZoomPageTransitionsBuilder().reverseTransitionDuration, + const _TestSlightRightPageTransitionsBuilder().reverseTransitionDuration, ); await observer.pumpPastTransition(tester); @@ -202,16 +194,15 @@ void main() { final observer = TransitionDurationObserver(); await tester.pumpWidget( - MaterialApp( + WidgetsApp( + color: const Color(0xFFFFFFFF), navigatorObservers: [observer], onGenerateRoute: (RouteSettings settings) { return switch (settings.name) { // A route with no transition. '/' => _TestOverlayRoute( builder: (BuildContext context) { - return const Scaffold( - body: Center(child: Column(children: [Text('Page 1')])), - ); + return const Center(child: Column(children: [Text('Page 1')])); }, ), _ => throw Exception('Invalid route.'), @@ -225,18 +216,78 @@ void main() { }); } -class _TestTransitionRoute extends MaterialPageRoute { +class _TestSlightRightPageTransitionsBuilder extends PageTransitionsBuilder { + const _TestSlightRightPageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + const begin = Offset(1.0, 0.0); + const Offset end = .zero; + final Animatable tween = Tween( + begin: begin, + end: end, + ).chain(CurveTween(curve: Curves.ease)); + + return SlideTransition( + position: animation.drive(tween), + child: FadeTransition(opacity: animation, child: child), + ); + } +} + +class _TestSlideUpPageTransitionsBuilder extends PageTransitionsBuilder { + const _TestSlideUpPageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + const begin = Offset(0.0, 1.0); + const Offset end = .zero; + final Animatable tween = Tween( + begin: begin, + end: end, + ).chain(CurveTween(curve: Curves.ease)); + + return SlideTransition( + position: animation.drive(tween), + child: FadeTransition(opacity: animation, child: child), + ); + } +} + +class _TestTransitionRoute extends PageRoute { _TestTransitionRoute({ - required super.builder, + required this.builder, required this.pageTransitionsBuilder, this.transitionDurationOverride, this.reverseTransitionDurationOverride, }); + final WidgetBuilder builder; final PageTransitionsBuilder pageTransitionsBuilder; final Duration? transitionDurationOverride; final Duration? reverseTransitionDurationOverride; + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return builder(context); + } + @override Widget buildTransitions( BuildContext context, @@ -253,6 +304,15 @@ class _TestTransitionRoute extends MaterialPageRoute { ); } + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + bool get maintainState => true; + @override Duration get transitionDuration => transitionDurationOverride ?? pageTransitionsBuilder.transitionDuration; diff --git a/packages/flutter_test/test/semantics_finder_test.dart b/packages/flutter_test/test/semantics_finder_test.dart index d64b619b00ad8..06253b4386c55 100644 --- a/packages/flutter_test/test/semantics_finder_test.dart +++ b/packages/flutter_test/test/semantics_finder_test.dart @@ -4,7 +4,7 @@ import 'dart:ui'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'multi_view_testing.dart'; diff --git a/packages/flutter_test/test/test_text_input_test.dart b/packages/flutter_test/test/test_text_input_test.dart index 4223d18fb380c..56778e40724ee 100644 --- a/packages/flutter_test/test/test_text_input_test.dart +++ b/packages/flutter_test/test/test_text_input_test.dart @@ -8,29 +8,8 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - PageRoute defaultPageRouteBuilder(RouteSettings settings, WidgetBuilder builder) { - return PageRouteBuilder( - settings: settings, - pageBuilder: - ( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - ) => builder(context), - transitionsBuilder: - ( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) => child, - ); - } - Widget buildTestApp({required TextEditingController controller, required FocusNode focusNode}) { - return WidgetsApp( - color: const Color(0xFFFFFFFF), - pageRouteBuilder: defaultPageRouteBuilder, + return TestWidgetsApp( home: SizedBox.expand( child: Center( child: EditableText( diff --git a/packages/flutter_test/test/utils/memory_leak_tests.dart b/packages/flutter_test/test/utils/memory_leak_tests.dart index 055304e8b7ae1..024795ba83899 100644 --- a/packages/flutter_test/test/utils/memory_leak_tests.dart +++ b/packages/flutter_test/test/utils/memory_leak_tests.dart @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; diff --git a/packages/flutter_test/test/widget_tester_live_device_test.dart b/packages/flutter_test/test/widget_tester_live_device_test.dart index 936b541a478bf..937f18e1b3c7d 100644 --- a/packages/flutter_test/test/widget_tester_live_device_test.dart +++ b/packages/flutter_test/test/widget_tester_live_device_test.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; // Only check the initial lines of the message, since the message walks the diff --git a/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart b/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart index 27710c6e4f17a..c929745d52609 100644 --- a/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart +++ b/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart.expect b/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart.expect index 5375732f1b272..b4c36e9d1cadd 100644 --- a/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart.expect +++ b/packages/flutter_test/test_fixes/flutter_test/animation_sheet_builder.dart.expect @@ -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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter_test/test_fixes/flutter_test/matchers.dart b/packages/flutter_test/test_fixes/flutter_test/matchers.dart index faafd100d74af..9c197c7e2d507 100644 --- a/packages/flutter_test/test_fixes/flutter_test/matchers.dart +++ b/packages/flutter_test/test_fixes/flutter_test/matchers.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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { diff --git a/packages/flutter_test/test_fixes/flutter_test/matchers.dart.expect b/packages/flutter_test/test_fixes/flutter_test/matchers.dart.expect index 6ee96f10d9e0e..495dbbacefb25 100644 --- a/packages/flutter_test/test_fixes/flutter_test/matchers.dart.expect +++ b/packages/flutter_test/test_fixes/flutter_test/matchers.dart.expect @@ -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 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { From 5473876cb6179b54d4281252f01b11e7b0acf2be Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 12:47:08 -0400 Subject: [PATCH 102/147] Roll Packages from 3e636359fb00 to dd11626c81a3 (11 revisions) (#190216) https://github.com/flutter/packages/compare/3e636359fb00...dd11626c81a3 2026-07-29 stuartmorgan@google.com [pigeon] Clean up legacy registrant code (flutter/packages#12205) 2026-07-29 me@davidmiguel.com [go_router] Add BlockedInitialNavigationException for blocked initial deep links (flutter/packages#12213) 2026-07-28 stuartmorgan@google.com [file_selector] Switch to Swift Testing (flutter/packages#12235) 2026-07-28 269567208+reidbaker-agent@users.noreply.github.com Exempt evals directories from version checks (flutter/packages#12299) 2026-07-28 dkwingsmt@users.noreply.github.com [material_ui, cupertino_ui] Migrate macros that contain `@tool` (flutter/packages#12271) 2026-07-28 katelovett@google.com [material_ui, cupertino_ui] Fix API doc references (flutter/packages#12274) 2026-07-28 43054281+camsim99@users.noreply.github.com [camera_android_camerax] Delete `GeneratedCameraxLibrary.java` (flutter/packages#12294) 2026-07-28 engine-flutter-autoroll@skia.org Roll Flutter from 99889603182d to 0f0246377b1c (16 revisions) (flutter/packages#12300) 2026-07-28 developeryusuf@icloud.com [vector_graphics_compiler] Ignore unrecognized font-weight values (flutter/packages#12199) 2026-07-28 stuartmorgan@google.com [tool] Support promoting to 1.0 in batch releases (flutter/packages#12262) 2026-07-28 1063596+reidbaker@users.noreply.github.com ci: Fix Zizmor github-env failure in install_flutter action (flutter/packages#12298) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index f49bb03e178fe..0432b4c82ea5b 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -3e636359fb007e63909fd4b656b07c7d4da3133a +dd11626c81a3972e1718dd3e9750195ebccbaa3d From 3e04a2d231267450a81f425840bf9c0062913bb0 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Wed, 29 Jul 2026 11:54:26 -0700 Subject: [PATCH 103/147] Convert all gradle-wrapper.properties in /dev to use distribution 'bin' (#190226) s/all/bin ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. --- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../stocks/android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../channels/android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../flavors/android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../ui/android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/gradle/wrapper/gradle-wrapper.properties | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/dev/a11y_assessments/android/gradle/wrapper/gradle-wrapper.properties b/dev/a11y_assessments/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/a11y_assessments/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/a11y_assessments/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/complex_layout/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/complex_layout/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/complex_layout/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/complex_layout/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/microbenchmarks/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/platform_views_layout/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/platform_views_layout/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/platform_views_layout/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/platform_views_layout/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/platform_views_layout_hybrid_composition/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/platform_views_layout_hybrid_composition/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/platform_views_layout_hybrid_composition/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/platform_views_layout_hybrid_composition/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/android_engine_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_engine_test/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/android_engine_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_engine_test/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/android_verified_input/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_verified_input/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/android_verified_input/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_verified_input/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/channels/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/channels/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/channels/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/channels/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/deferred_components_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/deferred_components_test/android/gradle/wrapper/gradle-wrapper.properties index 0c65c6543841e..2f2958b923a0a 100644 --- a/dev/integration_tests/deferred_components_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/deferred_components_test/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/dev/integration_tests/display_cutout_rotation/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/display_cutout_rotation/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/display_cutout_rotation/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/display_cutout_rotation/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/external_textures/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/external_textures/android/gradle/wrapper/gradle-wrapper.properties index 0c65c6543841e..2f2958b923a0a 100644 --- a/dev/integration_tests/external_textures/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/external_textures/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/flutter_gallery/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/flutter_gallery/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/flutter_gallery/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/flutter_gallery/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties index 56a30819350e2..3638a6c4d391e 100644 --- a/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-REPLACEME-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-REPLACEME-bin.zip diff --git a/dev/integration_tests/record_use_test_app/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/record_use_test_app/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/record_use_test_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/record_use_test_app/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/spell_check/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/spell_check/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/spell_check/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/spell_check/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties b/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties index a20f2c46d22ec..bdc0141f5311c 100644 --- a/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip diff --git a/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties b/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties index 0c65c6543841e..2f2958b923a0a 100644 --- a/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a54899ee83d032c03cd16545b403b06df8f37c2c Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 30 Jul 2026 06:39:01 +0900 Subject: [PATCH 104/147] iOS: Fix Swift warning in TracingTests (#190203) Fixes the following warning due to unnecessary use of `defer` on the last line of a scope: ./engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift:44:5: warning: 'defer' statement at end of scope always executes immediately; replace with 'do' statement to silence this warning 42 | @Test func testTraceScopeTokenDoesNotCrash() { 43 | let scope = Tracing.beginScope("TestScope") 44 | defer { scope.end() } | `- warning: 'defer' statement at end of scope always executes immediately; replace with 'do' statement to silence this warning 45 | } 46 | } ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../platform/darwin/common/framework/Source/TracingTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift b/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift index 20c193c529b9b..7efb53c35bafb 100644 --- a/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift +++ b/engine/src/flutter/shell/platform/darwin/common/framework/Source/TracingTests.swift @@ -41,7 +41,7 @@ import Testing @Test func testTraceScopeTokenDoesNotCrash() { let scope = Tracing.beginScope("TestScope") - defer { scope.end() } + scope.end() } } From b242a054ce9c727be54ea23d32dd4037c2698f72 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 18:26:09 -0400 Subject: [PATCH 105/147] Roll Skia from 0f35bba4945c to 6fea713d32ec (6 revisions) (#190230) https://skia.googlesource.com/skia.git/+log/0f35bba4945c..6fea713d32ec 2026-07-29 borenet@google.com [infra] Remove BuildStats jobs 2026-07-29 jmbetancourt@google.com Fix SkLog license 2026-07-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 863cb98140c0 to 952256d4e39e (1 revision) 2026-07-29 michaelludwig@google.com [graphite] Allow appending to binding lists within a layer when overlaps detected 2026-07-29 borenet@google.com [infra] Move all results uploads into the tasks themselves 2026-07-29 thomsmit@google.com [graphite] Rename GradientBuffer to StorageBuffer If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- engine/src/flutter/sky/packages/sky_engine/LICENSE | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/DEPS b/DEPS index 7deadb6fab880..0baf5043a49d5 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '0f35bba4945c0450629ae46621dffc821c6d6ce3', + 'skia_revision': '6fea713d32ec101877b670ce05ff8844265525fc', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds diff --git a/engine/src/flutter/sky/packages/sky_engine/LICENSE b/engine/src/flutter/sky/packages/sky_engine/LICENSE index 83ba711a25372..88f05bfe30c20 100644 --- a/engine/src/flutter/sky/packages/sky_engine/LICENSE +++ b/engine/src/flutter/sky/packages/sky_engine/LICENSE @@ -18207,13 +18207,6 @@ Copyright 2026 The ANGLE project authors. All Rights Reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file -------------------------------------------------------------------------------- -skia - -Copyright 2026 The Android Open Source Project - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. --------------------------------------------------------------------------------- angle Copyright The ANGLE Project Authors. All rights reserved. From a79793d530b775f6efd74a653ebcad2b681ee9f4 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:26:10 +0000 Subject: [PATCH 106/147] Adds analytics to macos impeller opt in/out (#190220) fixes https://github.com/flutter/flutter/issues/190164 This matches the logic that shows up in [`build_ios.dart`](https://github.com/flutter/flutter/blob/e769fffb6c3346381b62693fe6ca16bee5ad2144/packages/flutter_tools/lib/src/commands/build_ios.dart#L1100) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/macos/build_macos.dart | 22 ++- .../hermetic/build_macos_test.dart | 180 +++++++++++++++++- 2 files changed, 194 insertions(+), 8 deletions(-) diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index be2f85f45842f..23d183d62406b 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -18,6 +18,7 @@ import '../darwin/darwin.dart'; import '../features.dart'; import '../globals.dart' as globals; import '../ios/migrations/metal_api_validation_migration.dart'; +import '../ios/plist_parser.dart'; import '../ios/xcode_build_settings.dart'; import '../ios/xcodeproj.dart'; import '../migrations/swift_package_manager_gitignore_migration.dart'; @@ -283,10 +284,7 @@ Future buildMacOS({ [ '/usr/bin/env', ...xcodebuildCommandArgs, - if (xcodeWorkspace != null) ...[ - '-workspace', - xcodeWorkspace.path, - ] else ...[ + if (xcodeWorkspace != null) ...['-workspace', xcodeWorkspace.path] else ...[ '-project', xcodeProject.path, ], @@ -356,6 +354,22 @@ Future buildMacOS({ 'Built ${globals.fs.path.relative(outputDirectory.path)}$appSize', color: TerminalColor.green, ); + + final File builtInfoPlist = globals.fs.file( + globals.fs.path.join(outputDirectory.path, 'Contents', 'Info.plist'), + ); + final String plistPath = builtInfoPlist.existsSync() + ? builtInfoPlist.path + : flutterProject.macos.defaultHostInfoPlist.path; + final bool? impellerEnabled = globals.plistParser.getValueFromFile( + plistPath, + PlistParser.kFLTEnableImpellerKey, + ); + + final buildLabel = impellerEnabled == false + ? 'plist-impeller-disabled' + : 'plist-impeller-enabled'; + globals.analytics.send(Event.flutterBuildInfo(label: buildLabel, buildType: 'macos')); } await _writeCodeSizeAnalysis(buildInfo, sizeAnalyzer); final Duration elapsedDuration = sw.elapsed; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index bbc1ae2ede174..6416ef10b6dcd 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -20,6 +20,7 @@ import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build_macos.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/ios/plist_parser.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:unified_analytics/unified_analytics.dart'; @@ -166,10 +167,7 @@ void main() { if (hasWorkspace) ...[ '-workspace', flutterProject.macos.xcodeWorkspace!.path, - ] else ...[ - '-project', - flutterProject.macos.xcodeProject.path, - ], + ] else ...['-project', flutterProject.macos.xcodeProject.path], '-configuration', configuration, '-scheme', @@ -1629,4 +1627,178 @@ STDERR STUFF OperatingSystemUtils: () => FakeOperatingSystemUtils(hostPlatform: HostPlatform.darwin_x64), }, ); + + group('Analytics for impeller plist setting', () { + const plistContents = ''' + + + + + FLTEnableImpeller + + + +'''; + + testUsingContext( + 'Sends an analytics event when Impeller is enabled', + () async { + final command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: fileSystem, + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + config: FakeConfig(), + platform: FakePlatform(), + fileSystemUtils: FakeFileSystemUtils(), + terminal: FakeTerminal(), + plistParser: FakePlistParser(), + processUtils: FakeProcessUtils(), + processManager: FakeProcessManager.any(), + templateRenderer: FakeTemplateRenderer(), + xcode: FakeXcode(), + artifacts: FakeArtifacts(), + cache: FakeCache(), + flutterVersion: FakeFlutterVersion(), + ); + createMinimalMockProjectFiles(); + + await createTestCommandRunner(command).run(const ['build', 'macos', '--no-pub']); + + expect( + fakeAnalytics.sentEvents, + contains(Event.flutterBuildInfo(label: 'plist-impeller-enabled', buildType: 'macos')), + ); + }, + overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => + FakeProcessManager.list([setUpFakeXcodeBuildHandler('Release')]), + Platform: () => macosPlatform, + OperatingSystemUtils: () => FakeOperatingSystemUtils(hostPlatform: HostPlatform.darwin_x64), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + Pub: ThrowingPub.new, + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + Analytics: () => fakeAnalytics, + }, + ); + + testUsingContext( + 'Sends an analytics event when Impeller is disabled', + () async { + final command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: fileSystem, + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + config: FakeConfig(), + platform: FakePlatform(), + fileSystemUtils: FakeFileSystemUtils(), + terminal: FakeTerminal(), + plistParser: FakePlistParser(), + processUtils: FakeProcessUtils(), + processManager: FakeProcessManager.any(), + templateRenderer: FakeTemplateRenderer(), + xcode: FakeXcode(), + artifacts: FakeArtifacts(), + cache: FakeCache(), + flutterVersion: FakeFlutterVersion(), + ); + createMinimalMockProjectFiles(); + + fileSystem.file(fileSystem.path.join('usr', 'bin', 'plutil')).createSync(recursive: true); + + final File infoPlist = fileSystem.file( + fileSystem.path.join('macos', 'Runner', 'Info.plist'), + )..createSync(recursive: true); + + infoPlist.writeAsStringSync(plistContents); + + await createTestCommandRunner(command).run(const ['build', 'macos', '--no-pub']); + + expect( + fakeAnalytics.sentEvents, + contains(Event.flutterBuildInfo(label: 'plist-impeller-disabled', buildType: 'macos')), + ); + }, + overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => + FakeProcessManager.list([setUpFakeXcodeBuildHandler('Release')]), + Platform: () => macosPlatform, + OperatingSystemUtils: () => FakeOperatingSystemUtils(hostPlatform: HostPlatform.darwin_x64), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + Pub: ThrowingPub.new, + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + Analytics: () => fakeAnalytics, + PlistParser: () => FakePlistParser({'FLTEnableImpeller': false}), + }, + ); + + testUsingContext( + 'Reads built app bundle Contents/Info.plist when present', + () async { + final command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: fileSystem, + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + config: FakeConfig(), + platform: FakePlatform(), + fileSystemUtils: FakeFileSystemUtils(), + terminal: FakeTerminal(), + plistParser: FakePlistParser(), + processUtils: FakeProcessUtils(), + processManager: FakeProcessManager.any(), + templateRenderer: FakeTemplateRenderer(), + xcode: FakeXcode(), + artifacts: FakeArtifacts(), + cache: FakeCache(), + flutterVersion: FakeFlutterVersion(), + ); + createMinimalMockProjectFiles(); + + await createTestCommandRunner(command).run(const ['build', 'macos', '--no-pub']); + + expect( + fakeAnalytics.sentEvents, + contains(Event.flutterBuildInfo(label: 'plist-impeller-disabled', buildType: 'macos')), + ); + }, + overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list([ + setUpFakeXcodeBuildHandler( + 'Release', + onRun: (_) { + fileSystem + .file( + fileSystem.path.join( + 'build', + 'macos', + 'Build', + 'Products', + 'Release', + 'Runner.app', + 'Contents', + 'Info.plist', + ), + ) + .createSync(recursive: true); + }, + ), + ]), + Platform: () => macosPlatform, + OperatingSystemUtils: () => FakeOperatingSystemUtils(hostPlatform: HostPlatform.darwin_x64), + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + Pub: ThrowingPub.new, + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + Analytics: () => fakeAnalytics, + PlistParser: () => FakePlistParser({'FLTEnableImpeller': false}), + }, + ); + }); } From b1ae9ca144127fa87eb86d0cdf73f6c028964ec3 Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Wed, 29 Jul 2026 15:30:07 -0700 Subject: [PATCH 107/147] ci: mac verify binaries still has to wait on linux_host_engine (#190239) The `Linux linux_host_engine` builds the required engine stamp file; this is required for the tool to work correctly... without this extra wait, the workflow races mac builds vs linux builds. --- .github/workflows/mac-verify-binaries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac-verify-binaries.yml b/.github/workflows/mac-verify-binaries.yml index 33814f11eb95a..fafb784227276 100644 --- a/.github/workflows/mac-verify-binaries.yml +++ b/.github/workflows/mac-verify-binaries.yml @@ -46,7 +46,7 @@ jobs: uses: ./.github/actions/wait-for-engine-build with: github-token: ${{ secrets.GITHUB_TOKEN }} - check-name: 'Mac mac_host_engine' + check-name: 'Mac mac_host_engine,Linux linux_host_engine' - uses: ./.github/actions/composite-flutter-setup From f63d93bf3088ba05952d1dbc4663e016dd913e0d Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 30 Jul 2026 01:02:05 +0200 Subject: [PATCH 108/147] [cross imports] Check examples cross imports (#187662) This PR adds a check that checks examples under /examples for cross imports. Since most of the structure is shared with the existing cross imports checker, I opted to share some bits and pieces in a utils file as well. A new test suite was added, which mirrors most of the setup in the existing test. Currently the examples checker is too strict by design, as it does not allow any cross imports at all (including Material examples importing Cupertino). There are some edge cases that we should fix (such as context menu examples?) where both imports _are_ allowed. But for now, since we have no checks in place, I opted for strict first, exempt later, since this gives us a good starting point. I did notice that examples like `examples/api/lib/material/context_menu/editable_text_toolbar_builder.0.dart`, could definitely be cleaned up before we add exemptions. To be looked at later. Fixes https://github.com/flutter/flutter/issues/187645 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- dev/bots/check_examples_cross_imports.dart | 1121 ++++++++++++++++ dev/bots/check_tests_cross_imports.dart | 257 ++-- dev/bots/cross_imports_checker_utils.dart | 195 +++ .../check_examples_cross_imports_test.dart | 1130 +++++++++++++++++ .../test/check_tests_cross_imports_test.dart | 74 +- .../cross_imports_checker_test_utils.dart | 88 ++ 6 files changed, 2618 insertions(+), 247 deletions(-) create mode 100644 dev/bots/check_examples_cross_imports.dart create mode 100644 dev/bots/cross_imports_checker_utils.dart create mode 100644 dev/bots/test/check_examples_cross_imports_test.dart create mode 100644 dev/bots/test/cross_imports_checker_test_utils.dart diff --git a/dev/bots/check_examples_cross_imports.dart b/dev/bots/check_examples_cross_imports.dart new file mode 100644 index 0000000000000..699c150102f9e --- /dev/null +++ b/dev/bots/check_examples_cross_imports.dart @@ -0,0 +1,1121 @@ +// 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. + +// To run this, from the root of the Flutter repository: +// bin/cache/dart-sdk/bin/dart --enable-asserts dev/bots/check_examples_cross_imports.dart + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:path/path.dart' as path; + +import 'cross_imports_checker_utils.dart'; +import 'utils.dart'; + +final String _scriptLocation = path.fromUri(Platform.script); +final String _flutterRoot = path.dirname(path.dirname(path.dirname(_scriptLocation))); +final String _examplesDirectoryPath = path.join(_flutterRoot, 'examples'); + +void main(List args) { + final argParser = ArgParser(); + argParser.addFlag('help', negatable: false, help: 'Print help for this command.'); + argParser.addOption( + 'examples', + valueHelp: 'path', + defaultsTo: _examplesDirectoryPath, + help: 'A location where the examples are found.', + ); + argParser.addOption( + 'flutter-root', + valueHelp: 'path', + defaultsTo: _flutterRoot, + help: 'The path to the root of the Flutter repo.', + ); + final ArgResults parsedArgs; + + void usage() { + print('dart --enable-asserts ${path.basename(_scriptLocation)} [options]'); + print(argParser.usage); + } + + try { + parsedArgs = argParser.parse(args); + } on FormatException catch (e) { + print(e.message); + usage(); + exit(1); + } + + if (parsedArgs['help'] as bool) { + usage(); + exit(0); + } + + const FileSystem filesystem = LocalFileSystem(); + final Directory examplesDirectory = filesystem.directory(parsedArgs['examples']! as String); + final Directory flutterRoot = filesystem.directory(parsedArgs['flutter-root']! as String); + + final checker = ExamplesCrossImportChecker( + examplesDirectory: examplesDirectory, + flutterRoot: flutterRoot, + ); + + if (!checker.check()) { + reportErrorsAndExit('Some errors were found in the examples imports.'); + } + reportSuccessAndExit('No errors were detected with examples cross imports.'); +} + +/// Checks the examples in `examples/**` libraries for cross imports. +/// +/// Excludes known examples that contain cross imports, i.e. +/// [ExamplesCrossImportChecker.knownExamplesFlutterViewCrossImports] and +/// [ExamplesCrossImportChecker.knownExamplesImageListCrossImports]. +/// +/// No examples should import Material or Cupertino. +/// Any Material or Cupertino specific examples should go in +/// packages/material_ui or packages/cupertino_ui respectively. +class ExamplesCrossImportChecker { + ExamplesCrossImportChecker({ + required this.examplesDirectory, + required this.flutterRoot, + this.filesystem = const LocalFileSystem(), + }); + + final Directory examplesDirectory; + final Directory flutterRoot; + final FileSystem filesystem; + + static const String _kSampleTemplatesDirectoryName = 'sample_templates'; + + /// The known cross imports in the `examples/` directory, including subdirectories. + /// + /// These cross imports should all eventually be resolved, but until they are we allow them, so + /// that we can catch any new cross imports that are added. + // TODO(justinmc): Fix all of these tests so there are no cross imports. + // See https://github.com/flutter/flutter/issues/187645. + static final Set knownExamplesCrossImports = { + 'examples/api/lib/animation/animation_controller/animated_digit.0.dart', + 'examples/api/lib/animation/curves/curve2_d.0.dart', + 'examples/api/test/animation/animation_controller/animated_digit.0_test.dart', + 'examples/api/test/animation/curves/curve2_d.0_test.dart', + 'examples/api/lib/foundation/key/value_key.0.dart', + 'examples/api/test/foundation/key/value_key.0_test.dart', + 'examples/api/lib/gestures/pointer_signal_resolver/pointer_signal_resolver.0.dart', + 'examples/api/lib/gestures/tap_and_drag/tap_and_drag.0.dart', + 'examples/api/test/gestures/pointer_signal_resolver/pointer_signal_resolver.0_test.dart', + 'examples/api/test/gestures/tap_and_drag/tap_and_drag.0_test.dart', + 'examples/api/lib/painting/gradient/linear_gradient.0.dart', + 'examples/api/lib/painting/star_border/star_border.0.dart', + 'examples/api/lib/painting/axis_direction/axis_direction.0.dart', + 'examples/api/lib/painting/borders/border_side.stroke_align.0.dart', + 'examples/api/lib/painting/linear_border/linear_border.0.dart', + 'examples/api/lib/painting/image_provider/image_provider.0.dart', + 'examples/api/lib/painting/rounded_superellipse_border/rounded_superellipse_border.0.dart', + 'examples/api/test/painting/gradient/linear_gradient.0_test.dart', + 'examples/api/test/painting/star_border/star_border.0_test.dart', + 'examples/api/test/painting/axis_direction/axis_direction.0_test.dart', + 'examples/api/test/painting/borders/border_side.stroke_align.0_test.dart', + 'examples/api/test/painting/linear_border/linear_border.0_test.dart', + 'examples/api/test/painting/rounded_superellipse_border/rounded_superellipse_border.0_test.dart', + 'examples/api/lib/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0.dart', + 'examples/api/lib/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1.dart', + 'examples/api/lib/rendering/growth_direction/growth_direction.0.dart', + 'examples/api/lib/rendering/box/parent_data.0.dart', + 'examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart', + 'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1_test.dart', + 'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0_test.dart', + 'examples/api/test/rendering/growth_direction/growth_direction.0_test.dart', + 'examples/api/test/rendering/box/parent_data.0_test.dart', + 'examples/api/test/rendering/scroll_direction/scroll_direction.0_test.dart', + 'examples/api/lib/services/mouse_cursor/mouse_cursor.0.dart', + 'examples/api/lib/services/binding/handle_request_app_exit.0.dart', + 'examples/api/lib/services/text_input/text_input_control.0.dart', + 'examples/api/lib/services/keyboard_key/physical_keyboard_key.0.dart', + 'examples/api/lib/services/keyboard_key/logical_keyboard_key.0.dart', + 'examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.0.dart', + 'examples/api/lib/services/system_chrome/system_chrome.set_system_u_i_overlay_style.1.dart', + 'examples/api/test/services/text_input/text_input_control.0_test.dart', + 'examples/api/test/services/system_chrome/system_chrome.set_system_u_i_overlay_style.0_test.dart', + 'examples/api/test/services/system_chrome/system_chrome.set_system_u_i_overlay_style.1_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_ordinal_forms.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_contextual_alternates.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_alternative_fractions.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_oldstyle_figures.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_stylistic_alternates.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_historical_forms.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_stylistic_set.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_slashed_zero.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_superscripts.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_case_sensitive_forms.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_proportional_figures.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_historical_ligatures.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_tabular_figures.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_notational_forms.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_stylistic_set.1_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_alternative.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_locale_aware.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_fractions.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_denominator.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_lining_figures.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_numerators.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_swash.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_scientific_inferiors.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_subscripts.0_test.dart', + 'examples/api/test/ui/text/font_feature.font_feature_character_variant.0_test.dart', + 'examples/api/lib/widgets/animated_grid/sliver_animated_grid.0.dart', + 'examples/api/lib/widgets/animated_grid/animated_grid.0.dart', + 'examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.1.dart', + 'examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.0.dart', + 'examples/api/lib/widgets/editable_text/editable_text.on_changed.0.dart', + 'examples/api/lib/widgets/editable_text/text_editing_controller.0.dart', + 'examples/api/lib/widgets/editable_text/text_editing_controller.1.dart', + 'examples/api/lib/widgets/editable_text/editable_text.on_content_inserted.0.dart', + 'examples/api/lib/widgets/page/page_can_pop.0.dart', + 'examples/api/lib/widgets/undo_history/undo_history_controller.0.dart', + 'examples/api/lib/widgets/raw_tooltip/raw_tooltip.0.dart', + 'examples/api/lib/widgets/form/form.1.dart', + 'examples/api/lib/widgets/form/form.0.dart', + 'examples/api/lib/widgets/layout_builder/layout_builder.0.dart', + 'examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart', + 'examples/api/lib/widgets/restoration/restoration_mixin.0.dart', + 'examples/api/lib/widgets/app/widgets_app.widgets_app.0.dart', + 'examples/api/lib/widgets/drag_target/draggable.0.dart', + 'examples/api/lib/widgets/autocomplete/raw_autocomplete.focus_node.0.dart', + 'examples/api/lib/widgets/autocomplete/raw_autocomplete.2.dart', + 'examples/api/lib/widgets/autocomplete/raw_autocomplete.1.dart', + 'examples/api/lib/widgets/autocomplete/raw_autocomplete.0.dart', + 'examples/api/lib/widgets/keep_alive/automatic_keep_alive_client_mixin.0.dart', + 'examples/api/lib/widgets/keep_alive/automatic_keep_alive.0.dart', + 'examples/api/lib/widgets/keep_alive/keep_alive.0.dart', + 'examples/api/lib/widgets/safe_area/safe_area.0.dart', + 'examples/api/lib/widgets/scroll_notification_observer/scroll_notification_observer.0.dart', + 'examples/api/lib/widgets/animated_size/animated_size.0.dart', + 'examples/api/lib/widgets/framework/error_widget.0.dart', + 'examples/api/lib/widgets/framework/build_owner.0.dart', + 'examples/api/lib/widgets/sliver/pinned_header_sliver.1.dart', + 'examples/api/lib/widgets/sliver/sliver_list.0.dart', + 'examples/api/lib/widgets/sliver/pinned_header_sliver.0.dart', + 'examples/api/lib/widgets/sliver/sliver_constrained_cross_axis.0.dart', + 'examples/api/lib/widgets/sliver/sliver_tree.1.dart', + 'examples/api/lib/widgets/sliver/sliver_tree.0.dart', + 'examples/api/lib/widgets/sliver/sliver_floating_header.0.dart', + 'examples/api/lib/widgets/sliver/decorated_sliver.1.dart', + 'examples/api/lib/widgets/sliver/sliver_opacity.1.dart', + 'examples/api/lib/widgets/sliver/decorated_sliver.0.dart', + 'examples/api/lib/widgets/sliver/sliver_ensure_semantics.0.dart', + 'examples/api/lib/widgets/sliver/sliver_resizing_header.0.dart', + 'examples/api/lib/widgets/sliver/sliver_cross_axis_group.0.dart', + 'examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart', + 'examples/api/lib/widgets/heroes/hero.0.dart', + 'examples/api/lib/widgets/heroes/hero.1.dart', + 'examples/api/lib/widgets/dismissible/dismissible.0.dart', + 'examples/api/lib/widgets/overflow_bar/overflow_bar.0.dart', + 'examples/api/lib/widgets/preferred_size/preferred_size.0.dart', + 'examples/api/lib/widgets/media_query/media_query_data.system_gesture_insets.0.dart', + 'examples/api/lib/widgets/focus_traversal/ordered_traversal_policy.0.dart', + 'examples/api/lib/widgets/focus_traversal/focus_traversal_group.0.dart', + 'examples/api/lib/widgets/value_listenable_builder/value_listenable_builder.0.dart', + 'examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.1.dart', + 'examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.0.dart', + 'examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.3.dart', + 'examples/api/lib/widgets/raw_menu_anchor/raw_menu_anchor.2.dart', + 'examples/api/lib/widgets/async/stream_builder.0.dart', + 'examples/api/lib/widgets/async/future_builder.0.dart', + 'examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart', + 'examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart', + 'examples/api/lib/widgets/animated_list/animated_list_separated.0.dart', + 'examples/api/lib/widgets/animated_list/sliver_animated_list.0.dart', + 'examples/api/lib/widgets/animated_list/animated_list.0.dart', + 'examples/api/lib/widgets/basic/fractionally_sized_box.0.dart', + 'examples/api/lib/widgets/basic/physical_shape.0.dart', + 'examples/api/lib/widgets/basic/aspect_ratio.2.dart', + 'examples/api/lib/widgets/basic/flow.0.dart', + 'examples/api/lib/widgets/basic/aspect_ratio.0.dart', + 'examples/api/lib/widgets/basic/clip_rrect.0.dart', + 'examples/api/lib/widgets/basic/ignore_pointer.0.dart', + 'examples/api/lib/widgets/basic/fitted_box.0.dart', + 'examples/api/lib/widgets/basic/custom_multi_child_layout.0.dart', + 'examples/api/lib/widgets/basic/listener.0.dart', + 'examples/api/lib/widgets/basic/clip_rrect.1.dart', + 'examples/api/lib/widgets/basic/offstage.0.dart', + 'examples/api/lib/widgets/basic/aspect_ratio.1.dart', + 'examples/api/lib/widgets/basic/overflowbox.0.dart', + 'examples/api/lib/widgets/basic/indexed_stack.0.dart', + 'examples/api/lib/widgets/basic/mouse_region.on_exit.1.dart', + 'examples/api/lib/widgets/basic/expanded.1.dart', + 'examples/api/lib/widgets/basic/mouse_region.0.dart', + 'examples/api/lib/widgets/basic/expanded.0.dart', + 'examples/api/lib/widgets/basic/mouse_region.on_exit.0.dart', + 'examples/api/lib/widgets/basic/absorb_pointer.0.dart', + 'examples/api/lib/widgets/single_child_scroll_view/single_child_scroll_view.1.dart', + 'examples/api/lib/widgets/single_child_scroll_view/single_child_scroll_view.0.dart', + 'examples/api/lib/widgets/scroll_end_notification/scroll_end_notification.1.dart', + 'examples/api/lib/widgets/scroll_end_notification/scroll_end_notification.0.dart', + 'examples/api/lib/widgets/autofill/autofill_group.0.dart', + 'examples/api/lib/widgets/scroll_view/list_view.1.dart', + 'examples/api/lib/widgets/scroll_view/list_view.0.dart', + 'examples/api/lib/widgets/scroll_view/grid_view.0.dart', + 'examples/api/lib/widgets/scroll_view/custom_scroll_view.1.dart', + 'examples/api/lib/widgets/binding/widget_binding_observer.0.dart', + 'examples/api/lib/widgets/image/image.loading_builder.0.dart', + 'examples/api/lib/widgets/image/image.error_builder.0.dart', + 'examples/api/lib/widgets/image/image.frame_builder.0.dart', + 'examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart', + 'examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart', + 'examples/api/lib/widgets/slotted_render_object_widget/slotted_multi_child_render_object_widget_mixin.0.dart', + 'examples/api/lib/widgets/color_filter/color_filtered.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_padding.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_positioned.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_align.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_slide.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_container.0.dart', + 'examples/api/lib/widgets/implicit_animations/sliver_animated_opacity.0.dart', + 'examples/api/lib/widgets/implicit_animations/animated_fractionally_sized_box.0.dart', + 'examples/api/lib/widgets/radio_group/radio_group.0.dart', + 'examples/api/lib/widgets/inherited_theme/inherited_theme.0.dart', + 'examples/api/lib/widgets/scroll_position/scroll_metrics_notification.0.dart', + 'examples/api/lib/widgets/scroll_position/scroll_controller_on_attach.0.dart', + 'examples/api/lib/widgets/scroll_position/is_scrolling_listener.0.dart', + 'examples/api/lib/widgets/scroll_position/scroll_controller_notification.0.dart', + 'examples/api/lib/widgets/page_transitions_builder/page_transitions_builder.0.dart', + 'examples/api/lib/widgets/page_storage/page_storage.0.dart', + 'examples/api/lib/widgets/table/table.0.dart', + 'examples/api/lib/widgets/notification_listener/notification.0.dart', + 'examples/api/lib/widgets/inherited_model/inherited_model.0.dart', + 'examples/api/lib/widgets/focus_scope/focus.2.dart', + 'examples/api/lib/widgets/focus_scope/focus_scope.0.dart', + 'examples/api/lib/widgets/focus_scope/focus.1.dart', + 'examples/api/lib/widgets/focus_scope/focus.0.dart', + 'examples/api/lib/widgets/pop_scope/pop_scope.1.dart', + 'examples/api/lib/widgets/pop_scope/pop_scope.0.dart', + 'examples/api/lib/widgets/animated_switcher/animated_switcher.0.dart', + 'examples/api/lib/widgets/shortcuts/shortcuts.0.dart', + 'examples/api/lib/widgets/shortcuts/character_activator.0.dart', + 'examples/api/lib/widgets/shortcuts/shortcuts.1.dart', + 'examples/api/lib/widgets/shortcuts/callback_shortcuts.0.dart', + 'examples/api/lib/widgets/shortcuts/single_activator.0.dart', + 'examples/api/lib/widgets/shortcuts/logical_key_set.0.dart', + 'examples/api/lib/widgets/actions/action_listener.0.dart', + 'examples/api/lib/widgets/actions/action.action_overridable.0.dart', + 'examples/api/lib/widgets/actions/focusable_action_detector.0.dart', + 'examples/api/lib/widgets/actions/actions.0.dart', + 'examples/api/lib/widgets/widget_state/widget_state_property.0.dart', + 'examples/api/lib/widgets/widget_state/widget_state_border_side.0.dart', + 'examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart', + 'examples/api/lib/widgets/widget_state/widget_state_mouse_cursor.0.dart', + 'examples/api/lib/widgets/magnifier/magnifier.0.dart', + 'examples/api/lib/widgets/navigator/restorable_route_future.0.dart', + 'examples/api/lib/widgets/navigator/navigator.restorable_push_and_remove_until.0.dart', + 'examples/api/lib/widgets/navigator/navigator_state.restorable_push.0.dart', + 'examples/api/lib/widgets/navigator/navigator.restorable_push_replacement.0.dart', + 'examples/api/lib/widgets/navigator/navigator_state.restorable_push_and_remove_until.0.dart', + 'examples/api/lib/widgets/navigator/navigator_state.restorable_push_replacement.0.dart', + 'examples/api/lib/widgets/navigator/navigator.0.dart', + 'examples/api/lib/widgets/navigator/navigator.restorable_push.0.dart', + 'examples/api/lib/widgets/system_context_menu/system_context_menu.0.dart', + 'examples/api/lib/widgets/system_context_menu/system_context_menu.1.dart', + 'examples/api/lib/widgets/tween_animation_builder/tween_animation_builder.0.dart', + 'examples/api/lib/widgets/page_view/page_view.0.dart', + 'examples/api/lib/widgets/page_view/page_view.1.dart', + 'examples/api/lib/widgets/hardware_keyboard/key_event_manager.0.dart', + 'examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.2.dart', + 'examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.3.dart', + 'examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.0.dart', + 'examples/api/lib/widgets/sliver_fill/sliver_fill_remaining.1.dart', + 'examples/api/lib/widgets/interactive_viewer/interactive_viewer.builder.0.dart', + 'examples/api/lib/widgets/interactive_viewer/interactive_viewer.0.dart', + 'examples/api/lib/widgets/interactive_viewer/interactive_viewer.constrained.0.dart', + 'examples/api/lib/widgets/interactive_viewer/interactive_viewer.transformation_controller.0.dart', + 'examples/api/lib/widgets/text/ui_testing_with_text.dart', + 'examples/api/lib/widgets/text/text.0.dart', + 'examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.1.dart', + 'examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.0.dart', + 'examples/api/lib/widgets/nested_scroll_view/nested_scroll_view.2.dart', + 'examples/api/lib/widgets/nested_scroll_view/nested_scroll_view_state.0.dart', + 'examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart', + 'examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart', + 'examples/api/lib/widgets/transitions/positioned_transition.0.dart', + 'examples/api/lib/widgets/transitions/listenable_builder.3.dart', + 'examples/api/lib/widgets/transitions/matrix_transition.0.dart', + 'examples/api/lib/widgets/transitions/listenable_builder.2.dart', + 'examples/api/lib/widgets/transitions/size_transition.0.dart', + 'examples/api/lib/widgets/transitions/relative_positioned_transition.0.dart', + 'examples/api/lib/widgets/transitions/animated_builder.0.dart', + 'examples/api/lib/widgets/transitions/decorated_box_transition.0.dart', + 'examples/api/lib/widgets/transitions/rotation_transition.0.dart', + 'examples/api/lib/widgets/transitions/fade_transition.0.dart', + 'examples/api/lib/widgets/transitions/animated_widget.0.dart', + 'examples/api/lib/widgets/transitions/align_transition.0.dart', + 'examples/api/lib/widgets/transitions/listenable_builder.1.dart', + 'examples/api/lib/widgets/transitions/listenable_builder.0.dart', + 'examples/api/lib/widgets/transitions/default_text_style_transition.0.dart', + 'examples/api/lib/widgets/transitions/slide_transition.0.dart', + 'examples/api/lib/widgets/transitions/scale_transition.0.dart', + 'examples/api/lib/widgets/transitions/sliver_fade_transition.0.dart', + 'examples/api/lib/widgets/windows/popup.0.dart', + 'examples/api/lib/widgets/windows/tooltip.0.dart', + 'examples/api/lib/widgets/windows/satellite.0.dart', + 'examples/api/lib/widgets/text_editing_intents/editable_text_tap_up_outside_intent.0.dart', + 'examples/api/lib/widgets/overlay/overlay_portal.0.dart', + 'examples/api/lib/widgets/overlay/overlay.0.dart', + 'examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart', + 'examples/api/lib/widgets/focus_manager/focus_node.unfocus.0.dart', + 'examples/api/lib/widgets/focus_manager/focus_node.0.dart', + 'examples/api/lib/widgets/restoration_properties/restorable_value.0.dart', + 'examples/api/lib/widgets/gesture_detector/gesture_detector.3.dart', + 'examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart', + 'examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart', + 'examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart', + 'examples/api/lib/widgets/routes/show_general_dialog.0.dart', + 'examples/api/lib/widgets/routes/local_history_entry.0.dart', + 'examples/api/lib/widgets/routes/route_observer.0.dart', + 'examples/api/lib/widgets/routes/flexible_route_transitions.1.dart', + 'examples/api/lib/widgets/routes/flexible_route_transitions.0.dart', + 'examples/api/lib/widgets/routes/popup_route.0.dart', + 'examples/api/lib/widgets/repeating_animation_builder/repeating_animation_builder.0.dart', + 'examples/api/lib/widgets/scrollbar/raw_scrollbar.1.dart', + 'examples/api/lib/widgets/scrollbar/raw_scrollbar.shape.0.dart', + 'examples/api/lib/widgets/scrollbar/raw_scrollbar.0.dart', + 'examples/api/lib/widgets/scrollbar/raw_scrollbar.2.dart', + 'examples/api/lib/widgets/scrollbar/raw_scrollbar.desktop.0.dart', + 'examples/api/lib/widgets/inherited_notifier/inherited_notifier.0.dart', + 'examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart', + 'examples/api/test/widgets/animated_grid/animated_grid.0_test.dart', + 'examples/api/test/widgets/animated_grid/sliver_animated_grid.0_test.dart', + 'examples/api/test/widgets/navigator_pop_handler/navigator_pop_handler.1_test.dart', + 'examples/api/test/widgets/navigator_pop_handler/navigator_pop_handler.0_test.dart', + 'examples/api/test/widgets/editable_text/editable_text.on_content_inserted.0_test.dart', + 'examples/api/test/widgets/editable_text/text_editing_controller.1_test.dart', + 'examples/api/test/widgets/editable_text/editable_text.on_changed.0_test.dart', + 'examples/api/test/widgets/editable_text/text_editing_controller.0_test.dart', + 'examples/api/test/widgets/undo_history/undo_history_controller.0_test.dart', + 'examples/api/test/widgets/raw_tooltip/raw_tooltip.0_test.dart', + 'examples/api/test/widgets/form/form.0_test.dart', + 'examples/api/test/widgets/form/form.1_test.dart', + 'examples/api/test/widgets/tap_region/text_field_tap_region.0_test.dart', + 'examples/api/test/widgets/restoration/restoration_mixin.0_test.dart', + 'examples/api/test/widgets/drag_target/draggable.0_test.dart', + 'examples/api/test/widgets/autocomplete/raw_autocomplete.1_test.dart', + 'examples/api/test/widgets/autocomplete/raw_autocomplete.focus_node.0_test.dart', + 'examples/api/test/widgets/autocomplete/raw_autocomplete.0_test.dart', + 'examples/api/test/widgets/autocomplete/raw_autocomplete.2_test.dart', + 'examples/api/test/widgets/keep_alive/automatic_keep_alive.0_test.dart', + 'examples/api/test/widgets/keep_alive/keep_alive.0_test.dart', + 'examples/api/test/widgets/keep_alive/automatic_keep_alive_client_mixin.0_test.dart', + 'examples/api/test/widgets/safe_area/safe_area.0_test.dart', + 'examples/api/test/widgets/scroll_notification_observer/scroll_notification_observer.0_test.dart', + 'examples/api/test/widgets/animated_size/animated_size.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/sliver/pinned_header_sliver.1_test.dart', + 'examples/api/test/widgets/sliver/sliver_floating_header.0_test.dart', + 'examples/api/test/widgets/sliver/pinned_header_sliver.0_test.dart', + 'examples/api/test/widgets/sliver/sliver_opacity.1_test.dart', + 'examples/api/test/widgets/sliver/decorated_sliver.0_test.dart', + 'examples/api/test/widgets/sliver/sliver_cross_axis_group.0_test.dart', + 'examples/api/test/widgets/sliver/decorated_sliver.1_test.dart', + 'examples/api/test/widgets/sliver/sliver_main_axis_group.0_test.dart', + 'examples/api/test/widgets/sliver/sliver_constrained_cross_axis.0_test.dart', + 'examples/api/test/widgets/sliver/sliver_resizing_header.0_test.dart', + 'examples/api/test/widgets/heroes/hero.0_test.dart', + 'examples/api/test/widgets/heroes/hero.1_test.dart', + 'examples/api/test/widgets/dismissible/dismissible.0_test.dart', + 'examples/api/test/widgets/overflow_bar/overflow_bar.0_test.dart', + 'examples/api/test/widgets/preferred_size/preferred_size.0_test.dart', + 'examples/api/test/widgets/media_query/media_query_data.system_gesture_insets.0_test.dart', + 'examples/api/test/widgets/focus_traversal/focus_traversal_group.0_test.dart', + 'examples/api/test/widgets/focus_traversal/ordered_traversal_policy.0_test.dart', + 'examples/api/test/widgets/value_listenable_builder/value_listenable_builder.0_test.dart', + 'examples/api/test/widgets/raw_menu_anchor/raw_menu_anchor.3_test.dart', + 'examples/api/test/widgets/raw_menu_anchor/raw_menu_anchor.2_test.dart', + 'examples/api/test/widgets/raw_menu_anchor/raw_menu_anchor.0_test.dart', + 'examples/api/test/widgets/async/stream_builder.0_test.dart', + 'examples/api/test/widgets/async/future_builder.0_test.dart', + 'examples/api/test/widgets/shared_app_data/shared_app_data.0_test.dart', + 'examples/api/test/widgets/shared_app_data/shared_app_data.1_test.dart', + 'examples/api/test/widgets/animated_list/animated_list_separated.0_test.dart', + 'examples/api/test/widgets/animated_list/sliver_animated_list.0_test.dart', + 'examples/api/test/widgets/animated_list/animated_list.0_test.dart', + 'examples/api/test/widgets/basic/physical_shape.0_test.dart', + 'examples/api/test/widgets/basic/aspect_ratio.2_test.dart', + 'examples/api/test/widgets/basic/indexed_stack.0_test.dart', + 'examples/api/test/widgets/basic/clip_rrect.1_test.dart', + 'examples/api/test/widgets/basic/absorb_pointer.0_test.dart', + 'examples/api/test/widgets/basic/listener.0_test.dart', + 'examples/api/test/widgets/basic/clip_rrect.0_test.dart', + 'examples/api/test/widgets/basic/mouse_region.0_test.dart', + 'examples/api/test/widgets/basic/expanded.0_test.dart', + 'examples/api/test/widgets/basic/fitted_box.0_test.dart', + 'examples/api/test/widgets/basic/mouse_region.on_exit.0_test.dart', + 'examples/api/test/widgets/basic/aspect_ratio.0_test.dart', + 'examples/api/test/widgets/basic/fractionally_sized_box.0_test.dart', + 'examples/api/test/widgets/basic/expanded.1_test.dart', + 'examples/api/test/widgets/basic/mouse_region.on_exit.1_test.dart', + 'examples/api/test/widgets/basic/custom_multi_child_layout.0_test.dart', + 'examples/api/test/widgets/basic/aspect_ratio.1_test.dart', + 'examples/api/test/widgets/basic/flow.0_test.dart', + 'examples/api/test/widgets/basic/overflowbox.0_test.dart', + 'examples/api/test/widgets/scroll_end_notification/scroll_end_notification.0_test.dart', + 'examples/api/test/widgets/scroll_end_notification/scroll_end_notification.1_test.dart', + 'examples/api/test/widgets/autofill/autofill_group.0_test.dart', + 'examples/api/test/widgets/scroll_view/list_view.1_test.dart', + 'examples/api/test/widgets/scroll_view/custom_scroll_view.1_test.dart', + 'examples/api/test/widgets/scroll_view/list_view.0_test.dart', + 'examples/api/test/widgets/image/image.loading_builder.0_test.dart', + 'examples/api/test/widgets/image/image.error_builder.0_test.dart', + 'examples/api/test/widgets/image/image.frame_builder.0_test.dart', + 'examples/api/test/widgets/color_filter/color_filtered.0_test.dart', + 'examples/api/test/widgets/implicit_animations/animated_positioned.0_test.dart', + 'examples/api/test/widgets/implicit_animations/sliver_animated_opacity.0_test.dart', + 'examples/api/test/widgets/implicit_animations/animated_slide.0_test.dart', + 'examples/api/test/widgets/implicit_animations/animated_padding.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_container.0_test.dart', + 'examples/api/test/widgets/radio_group/radio_group.0_test.dart', + 'examples/api/test/widgets/inherited_theme/inherited_theme.0_test.dart', + 'examples/api/test/widgets/scroll_position/scroll_metrics_notification.0_test.dart', + 'examples/api/test/widgets/scroll_position/scroll_controller_on_attach.0_test.dart', + 'examples/api/test/widgets/scroll_position/is_scrolling_listener.0_test.dart', + 'examples/api/test/widgets/scroll_position/scroll_controller_notification.0_test.dart', + 'examples/api/test/widgets/page_transitions_builder/page_transitions_builder.0_test.dart', + 'examples/api/test/widgets/page_storage/page_storage.0_test.dart', + 'examples/api/test/widgets/table/table.0_test.dart', + 'examples/api/test/widgets/notification_listener/notification.0_test.dart', + 'examples/api/test/widgets/inherited_model/inherited_model.0_test.dart', + 'examples/api/test/widgets/focus_scope/focus.0_test.dart', + 'examples/api/test/widgets/focus_scope/focus.1_test.dart', + 'examples/api/test/widgets/focus_scope/focus.2_test.dart', + 'examples/api/test/widgets/focus_scope/focus_scope.0_test.dart', + 'examples/api/test/widgets/pop_scope/pop_scope.1_test.dart', + 'examples/api/test/widgets/animated_switcher/animated_switcher.0_test.dart', + 'examples/api/test/widgets/shortcuts/character_activator.0_test.dart', + 'examples/api/test/widgets/actions/action_listener.0_test.dart', + 'examples/api/test/widgets/actions/focusable_action_detector.0_test.dart', + 'examples/api/test/widgets/actions/actions.0_test.dart', + 'examples/api/test/widgets/actions/action.action_overridable.0_test.dart', + 'examples/api/test/widgets/widget_state/widget_state_property.0_test.dart', + 'examples/api/test/widgets/widget_state/widget_state_border_side.0_test.dart', + 'examples/api/test/widgets/widget_state/widget_state_outlined_border.0_test.dart', + 'examples/api/test/widgets/widget_state/widget_state_mouse_cursor.0_test.dart', + 'examples/api/test/widgets/magnifier/magnifier.0_test.dart', + 'examples/api/test/widgets/navigator/navigator.restorable_push.0_test.dart', + 'examples/api/test/widgets/navigator/navigator_state.restorable_push_and_remove_until.0_test.dart', + 'examples/api/test/widgets/navigator/navigator_state.restorable_push.0_test.dart', + 'examples/api/test/widgets/navigator/navigator.restorable_push_and_remove_until.0_test.dart', + 'examples/api/test/widgets/navigator/restorable_route_future.0_test.dart', + 'examples/api/test/widgets/navigator/navigator_state.restorable_push_replacement.0_test.dart', + 'examples/api/test/widgets/navigator/navigator.restorable_push_replacement.0_test.dart', + 'examples/api/test/widgets/system_context_menu/system_context_menu.1_test.dart', + 'examples/api/test/widgets/system_context_menu/system_context_menu.0_test.dart', + 'examples/api/test/widgets/tween_animation_builder/tween_animation_builder.0_test.dart', + 'examples/api/test/widgets/page_view/page_view.0_test.dart', + 'examples/api/test/widgets/page_view/page_view.1_test.dart', + 'examples/api/test/widgets/hardware_keyboard/key_event_manager.0_test.dart', + 'examples/api/test/widgets/sliver_fill/sliver_fill_remaining.1_test.dart', + 'examples/api/test/widgets/sliver_fill/sliver_fill_remaining.0_test.dart', + 'examples/api/test/widgets/sliver_fill/sliver_fill_remaining.3_test.dart', + 'examples/api/test/widgets/sliver_fill/sliver_fill_remaining.2_test.dart', + '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/interactive_viewer/interactive_viewer.constrained.0_test.dart', + 'examples/api/test/widgets/text/text.0_test.dart', + 'examples/api/test/widgets/nested_scroll_view/nested_scroll_view.2_test.dart', + 'examples/api/test/widgets/nested_scroll_view/nested_scroll_view_state.0_test.dart', + 'examples/api/test/widgets/nested_scroll_view/nested_scroll_view.0_test.dart', + 'examples/api/test/widgets/nested_scroll_view/nested_scroll_view.1_test.dart', + 'examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.0_test.dart', + 'examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.1_test.dart', + 'examples/api/test/widgets/transitions/listenable_builder.3_test.dart', + 'examples/api/test/widgets/transitions/sliver_fade_transition.0_test.dart', + 'examples/api/test/widgets/transitions/matrix_transition.0_test.dart', + 'examples/api/test/widgets/transitions/default_text_style_transition.0_test.dart', + 'examples/api/test/widgets/transitions/listenable_builder.2_test.dart', + 'examples/api/test/widgets/transitions/align_transition.0_test.dart', + 'examples/api/test/widgets/transitions/size_transition.0_test.dart', + 'examples/api/test/widgets/transitions/fade_transition.0_test.dart', + 'examples/api/test/widgets/transitions/listenable_builder.1_test.dart', + 'examples/api/test/widgets/transitions/relative_positioned_transition.0_test.dart', + 'examples/api/test/widgets/transitions/slide_transition.0_test.dart', + 'examples/api/test/widgets/transitions/positioned_transition.0_test.dart', + 'examples/api/test/widgets/transitions/decorated_box_transition.0_test.dart', + 'examples/api/test/widgets/transitions/animated_builder.0_test.dart', + 'examples/api/test/widgets/transitions/listenable_builder.0_test.dart', + 'examples/api/test/widgets/transitions/scale_transition.0_test.dart', + 'examples/api/test/widgets/transitions/animated_widget.0_test.dart', + 'examples/api/test/widgets/transitions/rotation_transition.0_test.dart', + 'examples/api/test/widgets/text_editing_intents/editable_text_tap_up_outside_intent.0_test.dart', + 'examples/api/test/widgets/overlay/overlay.0_test.dart', + 'examples/api/test/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.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/restoration_properties/restorable_value.0_test.dart', + 'examples/api/test/widgets/gesture_detector/gesture_detector.3_test.dart', + 'examples/api/test/widgets/gesture_detector/gesture_detector.2_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/routes/popup_route.0_test.dart', + 'examples/api/test/widgets/routes/show_general_dialog.0_test.dart', + 'examples/api/test/widgets/repeating_animation_builder/repeating_animation_builder.0_test.dart', + 'examples/api/test/widgets/scrollbar/raw_scrollbar.2_test.dart', + 'examples/api/test/widgets/scrollbar/raw_scrollbar.desktop.0_test.dart', + 'examples/api/test/widgets/scrollbar/raw_scrollbar.0_test.dart', + 'examples/api/test/widgets/scrollbar/raw_scrollbar.shape.0_test.dart', + 'examples/api/test/widgets/scrollbar/raw_scrollbar.1_test.dart', + 'examples/api/test/widgets/inherited_notifier/inherited_notifier.0_test.dart', + 'examples/api/test/widgets/text_magnifier/text_magnifier.0_test.dart', + 'examples/flutter_view/lib/main.dart', + 'examples/image_list/lib/main.dart', + 'examples/layers/rendering/touch_input.dart', + 'examples/layers/services/isolate.dart', + 'examples/layers/widgets/gestures.dart', + 'examples/layers/widgets/spinning_mixed.dart', + 'examples/layers/widgets/media_query.dart', + 'examples/layers/widgets/sectors.dart', + 'examples/layers/widgets/styled_text.dart', + 'examples/layers/test/gestures_test.dart', + 'examples/multiple_windows/lib/app/main_window.dart', + 'examples/multiple_windows/lib/app/tooltip_button.dart', + 'examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart', + 'examples/multiple_windows/lib/app/dialog_window_content.dart', + 'examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart', + 'examples/multiple_windows/lib/app/popup_window_content.dart', + 'examples/multiple_windows/lib/app/window_content.dart', + 'examples/multiple_windows/lib/app/window_edit_dialog.dart', + 'examples/multiple_windows/lib/app/rotated_wire_cube.dart', + 'examples/multiple_windows/lib/app/popup_window_edit_dialog.dart', + 'examples/multiple_windows/lib/app/tooltip_window_content.dart', + 'examples/multiple_windows/lib/app/popup_button.dart', + 'examples/multiple_windows/lib/app/window_settings_dialog.dart', + 'examples/multiple_windows/lib/main.dart', + 'examples/multiple_windows/test/multiple_windows_test.dart', + 'examples/platform_channel/lib/main.dart', + 'examples/platform_channel_swift/lib/main.dart', + 'examples/platform_view/lib/main.dart', + 'examples/splash/lib/main.dart', + 'examples/splash/test/splash_test.dart', + 'examples/texture/lib/main.dart', + }; + + static final RegExp _examplesPrefix = RegExp(r'examples'); + + /// Find the `examples/api/lib` and `examples/api/test` directories + /// which contain the API examples and relevant tests. + /// + /// For the cross imports checker, only the `examples/api/lib` and `examples/api/test` directories are relevant. + /// The other directories in `examples/api` are either generated (e.g. build or .dart_tool), + /// platform directories for the samples (e.g. windows or linux), + /// or a shim for the integration test driver. + ({Directory libDirectory, Directory testDirectory}) _findExamplesSlashApiDirectories( + Directory examplesSlashApiDirectory, + ) { + Directory? examplesSlashApiLibDirectory; + Directory? examplesSlashApiTestDirectory; + + for (final Directory directory in examplesSlashApiDirectory.listSync().whereType()) { + final String directoryName = path.basename(directory.absolute.path); + + if (directoryName == 'lib' && examplesSlashApiLibDirectory == null) { + examplesSlashApiLibDirectory = directory; + } else if (directoryName == 'test' && examplesSlashApiTestDirectory == null) { + examplesSlashApiTestDirectory = directory; + } + } + + if (examplesSlashApiLibDirectory == null) { + throw StateError('Could not find lib directory in examples/api.'); + } + + if (examplesSlashApiTestDirectory == null) { + throw StateError('Could not find test directory in examples/api.'); + } + + return ( + libDirectory: examplesSlashApiLibDirectory, + testDirectory: examplesSlashApiTestDirectory, + ); + } + + /// Get a list of all the filenames that end in ".dart", grouped by library. + Map<_ExamplesLibrary, Set> _getExampleFiles() { + final dartFilePattern = RegExp(r'\.dart$'); + + const _ExamplesLibrary examplesRoot = _GenericExampleLibrary('examples'); + final Map<_ExamplesLibrary, Set> mapping = {examplesRoot: {}}; + + // List the files directly under `examples` and then walk the subdirectories. + for (final FileSystemEntity fileSystemEntity in examplesDirectory.listSync()) { + if (fileSystemEntity is File && fileSystemEntity.absolute.path.contains(dartFilePattern)) { + mapping[examplesRoot]?.add(fileSystemEntity); + + continue; + } + + if (fileSystemEntity is! Directory) { + continue; + } + + final String directoryName = path.basename(fileSystemEntity.absolute.path); + + if (directoryName == 'build' || directoryName == '.dart_tool') { + continue; + } + + // The examples/api folder contains examples in a single Flutter project, + // grouped in subfolders in lib/ and test/, so these need to be handled separately. + if (directoryName == 'api') { + final examplesSlashApiLibrary = _ExamplesLibrary.fromDirectory( + fileSystemEntity, + flutterRoot: flutterRoot, + ); + + // First list the files directly under examples/api. + mapping[examplesSlashApiLibrary] = { + for (final File file in fileSystemEntity.listSync().whereType()) + if (file.absolute.path.contains(dartFilePattern)) file, + }; + + final (:Directory libDirectory, :Directory testDirectory) = + _findExamplesSlashApiDirectories(fileSystemEntity); + + // Handle the files under examples/api/lib/sample_templates and examples/api/test/sample_templates, + // which list individual files with a specific file pattern. + mapping.addAll( + _getExamplesSlashApiSampleTemplatesFiles( + libDirectory: libDirectory, + testDirectory: testDirectory, + dartFilePattern: dartFilePattern, + ), + ); + + // Handle the other samples, which are divided per subfolder. + mapping.addAll( + _getExamplesSlashApiExamples( + libDirectory: libDirectory, + testDirectory: testDirectory, + dartFilePattern: dartFilePattern, + ), + ); + + continue; + } + + final library = _ExamplesLibrary.fromDirectory(fileSystemEntity, flutterRoot: flutterRoot); + + mapping[library] = _getExampleFilesForDirectory( + fileSystemEntity, + dartFilePattern: dartFilePattern, + ); + } + + return mapping; + } + + /// Get a list of all the filenames that end in ".dart" for the given examples directory. + /// + /// The [directory] must not be a subdirectory of `examples/api`. + Set _getExampleFilesForDirectory(Directory directory, {required Pattern dartFilePattern}) { + final String examplesSlashApiPath = path.join(flutterRoot.absolute.path, 'examples', 'api'); + + if (directory.absolute.path.startsWith(examplesSlashApiPath)) { + throw ArgumentError('Directory must not be an examples/api subdirectory.', 'directory'); + } + + final files = {}; + final queue = [directory]; + + while (queue.isNotEmpty) { + final Directory current = queue.removeAt(0); + + for (final FileSystemEntity fileSystemEntity in current.listSync()) { + if (fileSystemEntity is File && fileSystemEntity.absolute.path.contains(dartFilePattern)) { + files.add(fileSystemEntity); + + continue; + } + + if (fileSystemEntity is! Directory) { + continue; + } + + final String directoryName = path.basename(fileSystemEntity.absolute.path); + + if (directoryName == 'build' || directoryName == '.dart_tool') { + continue; + } + + queue.add(fileSystemEntity); + } + } + + return files; + } + + /// Get a list of all the filenames that end in ".dart", grouped by library, + /// for the subdrectories of `examples/api/lib/sample_templates` and `examples/api/test/sample_templates`. + Map<_SampleTemplatesLibraryFile, Set> _getExamplesSlashApiSampleTemplatesFiles({ + required Directory libDirectory, + required Directory testDirectory, + required Pattern dartFilePattern, + }) { + final Directory sampleTemplatesLibDirectory = libDirectory.childDirectory( + _kSampleTemplatesDirectoryName, + ); + final Directory sampleTemplatesTestDirectory = testDirectory.childDirectory( + _kSampleTemplatesDirectoryName, + ); + + final Map<_SampleTemplatesLibraryFile, Set> mapping = {}; + + for (final File file + in sampleTemplatesLibDirectory.listSync(recursive: true).whereType()) { + if (file.absolute.path.contains(dartFilePattern)) { + mapping[_SampleTemplatesLibraryFile.fromFile(file)] = {file}; + } + } + + for (final File file + in sampleTemplatesTestDirectory.listSync(recursive: true).whereType()) { + if (file.absolute.path.contains(dartFilePattern)) { + mapping[_SampleTemplatesLibraryFile.fromFile(file)] = {file}; + } + } + + return mapping; + } + + /// Get a list of all the filenames that end in ".dart", grouped by library, + /// for the subdirectories of `examples/api`, + /// except `examples/api/lib/sample_templates` and `examples/api/test/sample_templates`. + Map<_ExamplesLibrary, Set> _getExamplesSlashApiExamples({ + required Directory libDirectory, + required Directory testDirectory, + required Pattern dartFilePattern, + }) { + final Map<_ExamplesLibrary, Set> mapping = {}; + + for (final Directory directory in libDirectory.listSync().whereType()) { + // The sample templates directory is handled separately. + if (path.basename(directory.absolute.path) == _kSampleTemplatesDirectoryName) { + continue; + } + + final library = _ExamplesLibrary.fromDirectory(directory, flutterRoot: flutterRoot); + + mapping.putIfAbsent(library, () => {}); + + for (final File file in directory.listSync(recursive: true).whereType()) { + if (!file.absolute.path.contains(dartFilePattern)) { + continue; + } + + mapping[library]?.add(file); + } + } + + for (final Directory directory in testDirectory.listSync().whereType()) { + // The sample templates directory is handled separately. + if (path.basename(directory.absolute.path) == _kSampleTemplatesDirectoryName) { + continue; + } + + final library = _ExamplesLibrary.fromDirectory(directory, flutterRoot: flutterRoot); + + mapping.putIfAbsent(library, () => {}); + + for (final File file in directory.listSync(recursive: true).whereType()) { + if (!file.absolute.path.contains(dartFilePattern)) { + continue; + } + + mapping[library]?.add(file); + } + } + + return mapping; + } + + /// Returns true if there are no errors, false otherwise. + bool check() { + filesystem.currentDirectory = flutterRoot; + + final Map<_ExamplesLibrary, Set> filesByLibrary = _getExampleFiles(); + + // Find all cross imports. + final Map crossImportsPerLibrary = + getCrossImports(filesByLibrary); + + var valid = true; + + // Find any cross imports that are not in the known list. + for (final MapEntry entry + in crossImportsPerLibrary.entries) { + final Set unknownCupertinoImports = getUnknowns( + knownExamplesCrossImports, + entry.value.cupertinoImports, + prefix: _examplesPrefix, + ); + final Set unknownMaterialImports = getUnknowns( + knownExamplesCrossImports, + entry.value.materialImports, + prefix: _examplesPrefix, + ); + + if (unknownMaterialImports.isNotEmpty) { + valid = false; + foundError( + getImportError( + flutterRoot: flutterRoot, + files: unknownMaterialImports, + checkedLibrary: entry.key, + importStatement: LibraryCrossImportStatementType.material, + ).split('\n'), + ); + } + + if (unknownCupertinoImports.isNotEmpty) { + valid = false; + foundError( + getImportError( + flutterRoot: flutterRoot, + files: unknownCupertinoImports, + checkedLibrary: entry.key, + importStatement: LibraryCrossImportStatementType.cupertino, + ).split('\n'), + ); + } + } + + // Find any known cross imports that weren't found, and are therefore fixed. + // Pre-compute all library prefixes so that root libraries (e.g. `examples`, + // `examples/api`) don't claim entries that belong to a more-specific sub-library. + // TODO(justinmc): Remove this after all known cross imports have been + // fixed. + // See https://github.com/flutter/flutter/issues/187645. + final Set allLibraryPrefixes = { + for (final CrossImportCheckedLibrary library in crossImportsPerLibrary.keys) + '${library.libraryName}/', + }; + + for (final MapEntry entry + in crossImportsPerLibrary.entries) { + final ownPrefix = '${entry.key.libraryName}/'; + + final Set crossImportsForLibrary = entry.value.cupertinoImports.union( + entry.value.materialImports, + ); + + final Set knownCrossImportsForLibrary = { + for (final String element in entry.key.knownCrossImports) + // The known cross imports include both /lib and /test entries, so handle both. + // Exclude entries that are owned by a more-specific sub-library. + if (element.startsWith(ownPrefix) && + !allLibraryPrefixes.any( + (String prefix) => + prefix != ownPrefix && + prefix.startsWith(ownPrefix) && + element.startsWith(prefix), + )) + element, + }; + + final Set fixedCrossImports = differencePaths( + knownCrossImportsForLibrary, + crossImportsForLibrary, + prefix: _examplesPrefix, + ); + + if (fixedCrossImports.isNotEmpty) { + valid = false; + foundError(getFixedImportError(fixedCrossImports, entry.key).split('\n')); + } + } + + return valid; + } +} + +/// The examples that we are concerned with cross importing. +sealed class _ExamplesLibrary implements CrossImportCheckedLibrary { + const _ExamplesLibrary(this._name); + + /// Construct a [_ExamplesLibrary] from a given [directory]. + /// + /// The [directory] must be inside the [flutterRoot]. + factory _ExamplesLibrary.fromDirectory(Directory directory, {required Directory flutterRoot}) { + if (!directory.absolute.path.startsWith(flutterRoot.absolute.path)) { + throw ArgumentError('Directory must be inside ${flutterRoot.absolute.path}.', 'directory'); + } + + final String relativePath = path + .relative(directory.absolute.path, from: flutterRoot.absolute.path) + .replaceAll(Platform.pathSeparator, '/'); + + return switch (relativePath) { + _ + when relativePath.startsWith('examples/api/lib/cupertino') || + relativePath.startsWith('examples/api/test/cupertino') => + _CupertinoApiExampleLibrary(relativePath), + + _ + when relativePath.startsWith('examples/api/lib/material') || + relativePath.startsWith('examples/api/test/material') => + _MaterialApiExampleLibrary(relativePath), + _ + when relativePath.startsWith('examples/api/lib/animation') || + relativePath.startsWith('examples/api/lib/foundation') || + relativePath.startsWith('examples/api/lib/gestures') || + relativePath.startsWith('examples/api/lib/painting') || + relativePath.startsWith('examples/api/lib/rendering') || + relativePath.startsWith('examples/api/lib/services') || + relativePath.startsWith('examples/api/lib/ui') || + relativePath.startsWith('examples/api/lib/widgets') => + _ApiExampleLibrary(relativePath), + _ + when relativePath.startsWith('examples/api/test/animation') || + relativePath.startsWith('examples/api/test/foundation') || + relativePath.startsWith('examples/api/test/gestures') || + relativePath.startsWith('examples/api/test/painting') || + relativePath.startsWith('examples/api/test/rendering') || + relativePath.startsWith('examples/api/test/services') || + relativePath.startsWith('examples/api/test/ui') || + relativePath.startsWith('examples/api/test/widgets') => + _ApiExampleLibrary(relativePath), + _ + when relativePath.startsWith('examples/flutter_view') || + relativePath.startsWith('examples/hello_world') || + relativePath.startsWith('examples/image_list') || + relativePath.startsWith('examples/layers') || + relativePath.startsWith('examples/multiple_windows') || + relativePath.startsWith('examples/platform_channel') || + relativePath.startsWith('examples/platform_channel_swift') || + relativePath.startsWith('examples/platform_view') || + relativePath.startsWith('examples/splash') || + relativePath.startsWith('examples/texture') => + _ApiExampleLibrary(relativePath), + _ when relativePath.startsWith('examples/api') || relativePath.startsWith('examples') => + _ApiExampleLibrary(relativePath), + _ => throw UnimplementedError('Unknown library: $relativePath'), + }; + } + + /// The short name of the library, for example `examples/flutter_view`. + final String _name; + + @override + String get cannotImportMessage { + return 'Only Material examples can import Material and only Cupertino examples can import Cupertino.'; + } + + @override + Set get knownCrossImports => ExamplesCrossImportChecker.knownExamplesCrossImports; + + @override + String get libraryName => _name; + + @override + String get removeCrossImportsInstructionMessage { + return 'However, they now need to be removed from the\n' + 'knownExamplesCrossImports list in the script /dev/bots/check_examples_cross_imports.dart.'; + } + + @override + bool canImport(LibraryCrossImportStatementType import) => false; + + @override + String getDisallowedImportMessage(String importedLibraryName, int filesCount) { + return filesCount < 2 + ? 'The following file in $libraryName has a disallowed import of $importedLibraryName. ' + 'Refactor it or move it to the $importedLibraryName examples.\n' + : 'The following $filesCount files in $libraryName have a disallowed import of $importedLibraryName. ' + 'Refactor them or move them to the $importedLibraryName examples.\n'; + } +} + +/// Any API example - not related to Material or Cupertino - inside `examples/api`, and its tests. +/// +/// For example `examples/api/lib/foundation` and `examples/api/test/foundation`. +final class _ApiExampleLibrary extends _ExamplesLibrary { + const _ApiExampleLibrary(super.name); +} + +/// The examples in `examples/api/lib/cupertino` +/// and their tests in `examples/api/test/cupertino`. +final class _CupertinoApiExampleLibrary extends _ExamplesLibrary { + const _CupertinoApiExampleLibrary(super.name); + + @override + bool canImport(LibraryCrossImportStatementType import) { + // While the Cupertino examples under `examples/api` are not allowed to import Material, + // the actual samples have been relocated to `packages/cupertino_ui`, + // where their cross imports will be addressed separately. + // The existing samples in `examples/api` are the now defunct orginal samples. + // For the purpose of the checker, allow all imports. + return true; + } +} + +/// Any non-API example, not in `examples/api`, +/// such as `examples/flutter_view` or `examples/hello_world`. +final class _GenericExampleLibrary extends _ExamplesLibrary { + const _GenericExampleLibrary(super.name); +} + +/// The examples in `examples/api/lib/material` +/// and their tests in `examples/api/test/material`. +final class _MaterialApiExampleLibrary extends _ExamplesLibrary { + const _MaterialApiExampleLibrary(super.name); + + @override + bool canImport(LibraryCrossImportStatementType import) { + // While the Material examples under `examples/api` are not allowed to import Cupertino, + // the actual samples have been relocated to `packages/material_ui`, + // where their cross imports will be addressed separately. + // The existing samples in `examples/api` are the now defunct orginal samples. + // For the purpose of the checker, allow all imports. + return true; + } +} + +/// The examples in `examples/api/lib/sample_templates` +/// and their tests in `examples/api/test/sample_templates`. +/// +/// The sample templates are individual files, rather than directories. +final class _SampleTemplatesLibraryFile extends _ExamplesLibrary { + const _SampleTemplatesLibraryFile._(super.name, this._filePath); + + factory _SampleTemplatesLibraryFile.fromFile(File file) { + const examplesLibPrefix = 'examples/api/lib/sample_templates'; + const examplesTestPrefix = 'examples/api/test/sample_templates'; + final String filePath = file.absolute.path.replaceAll(Platform.pathSeparator, '/'); + + final int libIndex = filePath.indexOf(examplesLibPrefix); + + if (libIndex != -1) { + return _SampleTemplatesLibraryFile._(examplesLibPrefix, filePath); + } + + final int testIndex = filePath.indexOf(examplesTestPrefix); + + if (testIndex != -1) { + return _SampleTemplatesLibraryFile._(examplesTestPrefix, filePath); + } + + throw ArgumentError('Invalid file path: $filePath'); + } + + /// The file path to the template file. + final String _filePath; + + @override + bool canImport(LibraryCrossImportStatementType import) { + return switch (import) { + LibraryCrossImportStatementType.material => _filePath.contains('material'), + LibraryCrossImportStatementType.cupertino => _filePath.contains('cupertino'), + }; + } +} diff --git a/dev/bots/check_tests_cross_imports.dart b/dev/bots/check_tests_cross_imports.dart index b51c2c6a69425..1810b2696d1c8 100644 --- a/dev/bots/check_tests_cross_imports.dart +++ b/dev/bots/check_tests_cross_imports.dart @@ -12,6 +12,7 @@ import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:path/path.dart' as path; +import 'cross_imports_checker_utils.dart'; import 'utils.dart'; final String _scriptLocation = path.fromUri(Platform.script); @@ -217,88 +218,6 @@ class TestsCrossImportChecker { // This matches both `packages/flutter/test` and `packages/flutter_test`. static final RegExp _flutterTestPrefix = RegExp(r'packages[/\\]flutter[/\\_]test'); - /// Returns the [Set] of paths in [knownPaths] that are not in [files]. - static Set _differencePaths(Set knownPaths, Set files) { - final Set testPaths = files.map((File file) { - final int index = file.absolute.path.indexOf(_flutterTestPrefix); - if (index < 0) { - throw ArgumentError('All files must include $_flutterTestPrefix in their path.', 'files'); - } - return file.absolute.path.substring(index).replaceAll(Platform.pathSeparator, '/'); - }).toSet(); - return knownPaths.difference(testPaths); - } - - /// Get the [Map] of files, per [_Library], of the files that have cross imports on Material and Cupertino. - static Map<_Library, _CrossImportingFiles> _getCrossImports(Map<_Library, Set> libraries) { - final Map<_Library, _CrossImportingFiles> crossImports = {}; - - for (final MapEntry<_Library, Set> entry in libraries.entries) { - final Set cupertinoImports = {}; - final Set materialImports = {}; - - for (final File file in entry.value) { - final String contents = file.readAsStringSync(); - - if (!entry.key.canImport(_LibraryImportStatement.cupertino) && - contents.contains(_LibraryImportStatement.cupertino.importString)) { - cupertinoImports.add(file); - } - - if (!entry.key.canImport(_LibraryImportStatement.material) && - contents.contains(_LibraryImportStatement.material.importString)) { - materialImports.add(file); - } - } - - crossImports[entry.key] = ( - cupertinoImports: cupertinoImports, - materialImports: materialImports, - ); - } - - return crossImports; - } - - /// Returns the [Set] of files that are not in [knownPaths]. - static Set _getUnknowns(Set knownPaths, Set files) { - return files.where((File file) { - final int index = file.absolute.path.indexOf(_flutterTestPrefix); - if (index < 0) { - throw ArgumentError('All files must include $_flutterTestPrefix in their path.', 'files'); - } - final String comparablePath = file.absolute.path - .substring(index) - .replaceAll(Platform.pathSeparator, '/'); - return !knownPaths.contains(comparablePath); - }).toSet(); - } - - /// Returns the error message for the given [fixedPaths] that no longer have a - /// cross import. - /// - /// The [library] must not be [_MaterialLibrary], because Material is allowed to - /// cross-import. - static String _getFixedImportError(Set fixedPaths, _Library library) { - assert(fixedPaths.isNotEmpty); - final buffer = StringBuffer( - 'Huzzah! The following tests in ${library.name} no longer contain cross imports!\n', - ); - for (final path in fixedPaths) { - buffer.writeln(' $path'); - } - buffer.writeln('However, they now need to be removed from the'); - buffer.write( - '${library.crossImportsListSymbolName} list in the script /dev/bots/check_tests_cross_imports.dart.', - ); - return buffer.toString().trimRight(); - } - - /// Returns the [file]'s relative path, relative to [flutterRoot]. - String _getRelativePath(File file) { - return path.relative(file.absolute.path, from: flutterRoot.absolute.path); - } - /// Get a list of all the filenames that end in ".dart", grouped by library. Map<_Library, Set> _getTestFiles() { final dartFilePattern = RegExp(r'\.dart$'); @@ -351,34 +270,6 @@ class TestsCrossImportChecker { return mapping; } - /// Returns the import error for the [files] in [testLibrary] which contain the given [importStatement]. - /// - /// Import errors only occur when: - /// - any library that is not Material or Cupertino, imports Material or Cupertino - /// - Cupertino imports Material - String _getImportError({ - required Set files, - required _Library testLibrary, - required _LibraryImportStatement importStatement, - }) { - assert( - !testLibrary.canImport(importStatement), - 'any library that is not Material or Cupertino, imports Material or Cupertino, ' - 'and when Cupertino imports Material.', - ); - - final String importedLibraryName = importStatement.readableName; - final buffer = StringBuffer( - files.length < 2 - ? 'The following test in ${testLibrary.name} has a disallowed import of $importedLibraryName. Refactor it or move it to $importedLibraryName.\n' - : 'The following ${files.length} tests in ${testLibrary.name} have a disallowed import of $importedLibraryName. Refactor them or move them to $importedLibraryName.\n', - ); - for (final file in files) { - buffer.writeln(' ${_getRelativePath(file).replaceAll(Platform.pathSeparator, '/')}'); - } - return buffer.toString().trimRight(); - } - /// Returns true if there are no errors, false otherwise. bool check() { filesystem.currentDirectory = flutterRoot; @@ -386,30 +277,33 @@ class TestsCrossImportChecker { final Map<_Library, Set> filesByLibrary = _getTestFiles(); // Find all cross imports. - final Map<_Library, _CrossImportingFiles> crossImportsPerLibrary = _getCrossImports( - filesByLibrary, - ); + final Map crossImportsPerLibrary = + getCrossImports(filesByLibrary); var valid = true; // Find any cross imports that are not in the known list. - for (final MapEntry<_Library, _CrossImportingFiles> entry in crossImportsPerLibrary.entries) { - final Set unknownCupertinoImports = _getUnknowns( + for (final MapEntry entry + in crossImportsPerLibrary.entries) { + final Set unknownCupertinoImports = getUnknowns( _knownCrossImports, entry.value.cupertinoImports, + prefix: _flutterTestPrefix, ); - final Set unknownMaterialImports = _getUnknowns( + final Set unknownMaterialImports = getUnknowns( _knownCrossImports, entry.value.materialImports, + prefix: _flutterTestPrefix, ); if (unknownMaterialImports.isNotEmpty) { valid = false; foundError( - _getImportError( + getImportError( + flutterRoot: flutterRoot, files: unknownMaterialImports, - testLibrary: entry.key, - importStatement: _LibraryImportStatement.material, + checkedLibrary: entry.key, + importStatement: LibraryCrossImportStatementType.material, ).split('\n'), ); } @@ -417,10 +311,11 @@ class TestsCrossImportChecker { if (unknownCupertinoImports.isNotEmpty) { valid = false; foundError( - _getImportError( + getImportError( + flutterRoot: flutterRoot, files: unknownCupertinoImports, - testLibrary: entry.key, - importStatement: _LibraryImportStatement.cupertino, + checkedLibrary: entry.key, + importStatement: LibraryCrossImportStatementType.cupertino, ).split('\n'), ); } @@ -430,19 +325,21 @@ class TestsCrossImportChecker { // TODO(justinmc): Remove this after all known cross imports have been // fixed. // See https://github.com/flutter/flutter/issues/177028. - for (final MapEntry<_Library, _CrossImportingFiles> entry in crossImportsPerLibrary.entries) { + for (final MapEntry entry + in crossImportsPerLibrary.entries) { final Set crossImportsForLibrary = entry.value.cupertinoImports.union( entry.value.materialImports, ); final Set knownCrossImportsForLibrary = entry.key.knownCrossImports; - final Set fixedCrossImports = _differencePaths( + final Set fixedCrossImports = differencePaths( knownCrossImportsForLibrary, crossImportsForLibrary, + prefix: _flutterTestPrefix, ); if (fixedCrossImports.isNotEmpty) { valid = false; - foundError(_getFixedImportError(fixedCrossImports, entry.key).split('\n')); + foundError(getFixedImportError(fixedCrossImports, entry.key).split('\n')); } } @@ -450,22 +347,9 @@ class TestsCrossImportChecker { } } -/// The set of files that import Cupertino and Material for a given [_Library]. -typedef _CrossImportingFiles = ({Set cupertinoImports, Set materialImports}); - -enum _LibraryImportStatement { - material('Material', "import 'package:flutter/material.dart'"), - cupertino('Cupertino', "import 'package:flutter/cupertino.dart'"); - - const _LibraryImportStatement(this.readableName, this.importString); - - final String readableName; - final String importString; -} - /// The libraries that we are concerned with cross importing. -sealed class _Library { - const _Library(this.name); +sealed class _Library implements CrossImportCheckedLibrary { + const _Library(this._name); /// Construct a [_Library] from a given [directory]. /// @@ -487,38 +371,15 @@ sealed class _Library { } /// The short name of the library, for example `packages/flutter/test/widgets`. - final String name; + final String _name; - /// The name of the variable in [TestsCrossImportChecker] - /// that contains the list of known cross imports for this library. - /// - /// This is used for reporting mismatched cross imports. - String get crossImportsListSymbolName { - return switch (name) { - 'packages/flutter_test' => 'knownFlutterTestLibraryCrossImports', - 'packages/flutter/test' => 'knownFlutterSlashTestCrossImports', - 'packages/flutter/test/animation' => 'knownAnimationCrossImports', - 'packages/flutter/test/cupertino' => 'knownCupertinoCrossImports', - 'packages/flutter/test/dart' => 'knownDartCrossImports', - 'packages/flutter/test/examples' => 'knownExamplesCrossImports', - 'packages/flutter/test/foundation' => 'knownFoundationCrossImports', - 'packages/flutter/test/gestures' => 'knownGesturesCrossImports', - 'packages/flutter/test/harness' => 'knownHarnessCrossImports', - 'packages/flutter/test/material' => throw UnsupportedError( - 'Material is responsible for testing its interactions with Cupertino, so it is allowed to cross-import.', - ), - 'packages/flutter/test/painting' => 'knownPaintingCrossImports', - 'packages/flutter/test/physics' => 'knownPhysicsCrossImports', - 'packages/flutter/test/rendering' => 'knownRenderingCrossImports', - 'packages/flutter/test/scheduler' => 'knownSchedulerCrossImports', - 'packages/flutter/test/semantics' => 'knownSemanticsCrossImports', - 'packages/flutter/test/services' => 'knownServicesCrossImports', - 'packages/flutter/test/widgets' => 'knownWidgetsCrossImports', - _ => throw UnimplementedError('Unknown library: $name'), - }; + @override + String get cannotImportMessage { + return 'any library that is not Material or Cupertino, imports Material or Cupertino, ' + 'and when Cupertino imports Material.'; } - /// Get the list of known cross imports for this [_Library]. + @override Set get knownCrossImports { // Material is allowed to cross import. if (this is _MaterialLibrary) { @@ -544,19 +405,65 @@ sealed class _Library { 'knownSemanticsCrossImports' => TestsCrossImportChecker.knownSemanticsCrossImports, 'knownServicesCrossImports' => TestsCrossImportChecker.knownServicesCrossImports, 'knownWidgetsCrossImports' => TestsCrossImportChecker.knownWidgetsCrossImports, - _ => throw UnimplementedError('Unknown library: $name'), + _ => throw UnimplementedError('Unknown library: $libraryName'), }; } - /// Returns whether this library can contain the given [import]. - bool canImport(_LibraryImportStatement import) { + @override + String get libraryName => _name; + + @override + String get removeCrossImportsInstructionMessage { + return 'However, they now need to be removed from the\n' + '$crossImportsListSymbolName list in the script /dev/bots/check_tests_cross_imports.dart.'; + } + + @override + bool canImport(LibraryCrossImportStatementType import) { return switch (this) { - _MaterialLibrary() => - import == _LibraryImportStatement.material || import == _LibraryImportStatement.cupertino, - _CupertinoLibrary() => import == _LibraryImportStatement.cupertino, + _MaterialLibrary() => import == .material || import == .cupertino, + _CupertinoLibrary() => import == .cupertino, _OtherLibrary() => false, }; } + + @override + String getDisallowedImportMessage(String importedLibraryName, int filesCount) { + return filesCount < 2 + ? 'The following test in $libraryName has a disallowed import of $importedLibraryName. ' + 'Refactor it or move it to $importedLibraryName.\n' + : 'The following $filesCount tests in $libraryName have a disallowed import of $importedLibraryName. ' + 'Refactor them or move them to $importedLibraryName.\n'; + } + + /// The name of the variable in [TestsCrossImportChecker] + /// that contains the list of known cross imports for this library. + /// + /// This is used for reporting mismatched cross imports. + String get crossImportsListSymbolName { + return switch (libraryName) { + 'packages/flutter_test' => 'knownFlutterTestLibraryCrossImports', + 'packages/flutter/test' => 'knownFlutterSlashTestCrossImports', + 'packages/flutter/test/animation' => 'knownAnimationCrossImports', + 'packages/flutter/test/cupertino' => 'knownCupertinoCrossImports', + 'packages/flutter/test/dart' => 'knownDartCrossImports', + 'packages/flutter/test/examples' => 'knownExamplesCrossImports', + 'packages/flutter/test/foundation' => 'knownFoundationCrossImports', + 'packages/flutter/test/gestures' => 'knownGesturesCrossImports', + 'packages/flutter/test/harness' => 'knownHarnessCrossImports', + 'packages/flutter/test/material' => throw UnsupportedError( + 'Material is responsible for testing its interactions with Cupertino, so it is allowed to cross-import.', + ), + 'packages/flutter/test/painting' => 'knownPaintingCrossImports', + 'packages/flutter/test/physics' => 'knownPhysicsCrossImports', + 'packages/flutter/test/rendering' => 'knownRenderingCrossImports', + 'packages/flutter/test/scheduler' => 'knownSchedulerCrossImports', + 'packages/flutter/test/semantics' => 'knownSemanticsCrossImports', + 'packages/flutter/test/services' => 'knownServicesCrossImports', + 'packages/flutter/test/widgets' => 'knownWidgetsCrossImports', + _ => throw UnimplementedError('Unknown library: $libraryName'), + }; + } } /// The Material library, also known as `packages/flutter/test/material`. diff --git a/dev/bots/cross_imports_checker_utils.dart b/dev/bots/cross_imports_checker_utils.dart new file mode 100644 index 0000000000000..da55972cc1296 --- /dev/null +++ b/dev/bots/cross_imports_checker_utils.dart @@ -0,0 +1,195 @@ +// 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 Platform; + +import 'package:file/file.dart'; +import 'package:path/path.dart' as path; + +/// A typedef that contains a set of [File]s which import Cupertino, +/// and a set of [File]s which import Material. +typedef CrossImportingFiles = ({Set cupertinoImports, Set materialImports}); + +/// A library that is checked for cross imports. +abstract interface class CrossImportCheckedLibrary { + /// Get the error message that is used to assert in [getImportError], + /// that an import error is expected. + /// + /// This error message should describe why an import is not allowed. + /// + /// For example: "Only Material is allowed to import Material" + String get cannotImportMessage; + + /// Get the list of known cross imports for this [CrossImportCheckedLibrary]. + Set get knownCrossImports; + + /// The short name of the library. + /// + /// For example `packages/flutter/test/widgets` or `examples/api/foo`. + String get libraryName; + + /// The message that instructs how to remove now-fixed cross imports for this library, + /// in the relevant cross imports checker. + /// + /// For example: + /// "However, they now need to be removed from the\n cross imports list in the script /dev/bots/check_tests_cross_imports.dart." + String get removeCrossImportsInstructionMessage; + + /// Returns whether this library may contain the given [import]. + bool canImport(LibraryCrossImportStatementType import); + + /// Get an error message that describes that [filesCount] files + /// have a disallowed import of [importedLibraryName]. + /// + /// This message is used as preamble when listing the import errors emitted by a cross imports checker. + /// + /// For example: + /// "The following $filesCount files have a disallowed import of $importedLibraryName. Refactor them or move them to $importedLibraryName." + String getDisallowedImportMessage(String importedLibraryName, int filesCount); +} + +/// An enum that defines the possible cross import statements for libraries, +/// which are of special interest for a cross imports checker. +enum LibraryCrossImportStatementType { + /// A cross import of the Material library. + material('Material', "import 'package:flutter/material.dart'"), + + /// A cross import of the Cupertino library. + cupertino('Cupertino', "import 'package:flutter/cupertino.dart'"); + + const LibraryCrossImportStatementType(this.readableName, this.importString); + + /// The readable name for the library that is being cross imported. + /// + /// For example `Material` or `Cupertino`. + final String readableName; + + /// The import statement string for a cross import of this type. + /// + /// This string is a valid Dart import statement, + /// for example `import 'package:flutter/material.dart'`. + /// + /// This import statement does not include a trailing semicolon, + /// as there may be a `show` keyword following the import statement in the affected library. + final String importString; +} + +/// Returns the [Set] of paths in [knownPaths] that are not in [files]. +/// +/// Each file is expected to have a path that includes [prefix]. +Set differencePaths(Set knownPaths, Set files, {required Pattern prefix}) { + final Set testPaths = files.map((File file) { + final int index = file.absolute.path.indexOf(prefix); + if (index < 0) { + throw ArgumentError('All files must include $prefix in their path.', 'files'); + } + + return file.absolute.path.substring(index).replaceAll(Platform.pathSeparator, '/'); + }).toSet(); + + return knownPaths.difference(testPaths); +} + +/// Get the [Map] of files, per [CrossImportCheckedLibrary], +/// of the files that have cross imports on Material and Cupertino. +Map getCrossImports( + Map> libraries, +) { + final Map crossImports = {}; + + for (final MapEntry> entry in libraries.entries) { + final Set cupertinoImports = {}; + final Set materialImports = {}; + + for (final File file in entry.value) { + final String contents = file.readAsStringSync(); + + for (final LibraryCrossImportStatementType importStatement + in LibraryCrossImportStatementType.values) { + switch (importStatement) { + case .cupertino: + if (!entry.key.canImport(importStatement) && + contents.contains(importStatement.importString)) { + cupertinoImports.add(file); + } + case .material: + if (!entry.key.canImport(importStatement) && + contents.contains(importStatement.importString)) { + materialImports.add(file); + } + } + } + } + + crossImports[entry.key] = ( + cupertinoImports: cupertinoImports, + materialImports: materialImports, + ); + } + + return crossImports; +} + +/// Returns the error message for the given [fixedPaths] +/// that no longer have a cross import. +/// +/// The [library] must not be the Material library, +/// because Material is allowed to cross-import. +String getFixedImportError(Set fixedPaths, CrossImportCheckedLibrary library) { + assert(fixedPaths.isNotEmpty); + final buffer = StringBuffer( + 'Huzzah! The following files in ${library.libraryName} no longer contain cross imports!\n', + ); + for (final path in fixedPaths) { + buffer.writeln(' $path'); + } + buffer.write(library.removeCrossImportsInstructionMessage); + + return buffer.toString().trimRight(); +} + +/// Returns the import error for the [files] in [checkedLibrary] which contain the given [importStatement]. +String getImportError({ + required Set files, + required Directory flutterRoot, + required CrossImportCheckedLibrary checkedLibrary, + required LibraryCrossImportStatementType importStatement, +}) { + assert(!checkedLibrary.canImport(importStatement), checkedLibrary.cannotImportMessage); + + final String importedLibraryName = importStatement.readableName; + final buffer = StringBuffer( + checkedLibrary.getDisallowedImportMessage(importedLibraryName, files.length), + ); + for (final file in files) { + buffer.writeln( + ' ${getRelativePath(file, flutterRoot: flutterRoot).replaceAll(Platform.pathSeparator, '/')}', + ); + } + return buffer.toString().trimRight(); +} + +/// Returns the [file]'s relative path, relative to [flutterRoot]. +String getRelativePath(File file, {required Directory flutterRoot}) { + return path.relative(file.absolute.path, from: flutterRoot.absolute.path); +} + +/// Returns the [Set] of files that are not in [knownPaths]. +/// +/// Each file is expected to have a path that includes [prefix]. +Set getUnknowns(Set knownPaths, Set files, {required Pattern prefix}) { + return files.where((File file) { + final int index = file.absolute.path.indexOf(prefix); + + if (index < 0) { + throw ArgumentError('All files must include $prefix in their path.', 'files'); + } + + final String comparablePath = file.absolute.path + .substring(index) + .replaceAll(Platform.pathSeparator, '/'); + + return !knownPaths.contains(comparablePath); + }).toSet(); +} diff --git a/dev/bots/test/check_examples_cross_imports_test.dart b/dev/bots/test/check_examples_cross_imports_test.dart new file mode 100644 index 0000000000000..809cd6b4be40d --- /dev/null +++ b/dev/bots/test/check_examples_cross_imports_test.dart @@ -0,0 +1,1130 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:path/path.dart' as path; + +import '../check_examples_cross_imports.dart'; +import '../cross_imports_checker_utils.dart'; +import 'common.dart'; +import 'cross_imports_checker_test_utils.dart'; + +// A pattern that matches `examples/api/lib/**` and `examples/api/test/**`, for use in tests. +final _kExamplesSlashApiLibraryPattern = RegExp(r'^examples/api/(lib|test)/[a-z_]+'); + +void main() { + late ExamplesCrossImportChecker checker; + late _CrossImportsExamplesDirectories checkerDirectories; + + void buildKnownCrossImportExamplesFiles({Set excludes = const {}}) { + final Map> knownFiles = checkerDirectories.getKnownFiles( + checker.examplesDirectory, + ); + + for (final Set files in knownFiles.values) { + for (final filePath in files) { + if (excludes.contains(filePath)) { + continue; + } + + final File file = checker.filesystem.file( + path.join( + checker.flutterRoot.absolute.path, + filePath.replaceAll('/', Platform.pathSeparator), + ), + ); + + final LibraryCrossImportStatementType importStatement = + getCrossImportStatementForExamplesLibraryFile(filePath); + + writeImport(file, importStatement.importString); + } + } + } + + setUp(() { + final fs = MemoryFileSystem( + style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, + ); + // Get the root prefix of the current directory so that on Windows we get a + // correct root prefix. + final Directory flutterRoot = fs.directory( + path.join(path.rootPrefix(fs.currentDirectory.absolute.path), 'flutter sdk'), + )..createSync(recursive: true); + fs.currentDirectory = flutterRoot; + + final Directory examplesDirectory = flutterRoot.childDirectory('examples')..createSync(); + + checker = ExamplesCrossImportChecker( + examplesDirectory: examplesDirectory, + flutterRoot: flutterRoot, + filesystem: fs, + ); + checkerDirectories = _CrossImportsExamplesDirectories(examplesDirectory) + ..createExamplesDirectories(examplesDirectory); + }); + + test('when only all knowns have cross imports', () async { + buildKnownCrossImportExamplesFiles(); + bool? success; + final String result = await capture(() async { + success = checker.check(); + }); + expect(result, equals('')); + expect(success, isTrue); + }); + + test('non-Dart files are ignored', () async { + buildKnownCrossImportExamplesFiles(); + + checker.examplesDirectory.childFile('README.md') + ..createSync() + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('non-Dart files are ignored in nested directories', () async { + buildKnownCrossImportExamplesFiles(); + + checker.examplesDirectory + .childDirectory('layers') + .childDirectory('rendering') + .childFile('README.md') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('non-Dart files with .dart in the filename are ignored', () async { + buildKnownCrossImportExamplesFiles(); + + checker.examplesDirectory.childFile('foo.dart.md') + ..createSync() + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('non-Dart files with .dart in the filename are ignored in nested directories', () async { + buildKnownCrossImportExamplesFiles(); + + checker.examplesDirectory + .childDirectory('layers') + .childDirectory('rendering') + .childFile('foo.dart.md') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('examples/api/lib/sample_templates templates produce no violations when valid', () async { + final Directory sampleTemplatesDirectory = checker.examplesDirectory + .childDirectory('api') + .childDirectory('lib') + .childDirectory('sample_templates'); + + for (final i in [0, 1, 2]) { + sampleTemplatesDirectory.childFile('cupertino.$i.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + sampleTemplatesDirectory.childFile('material.$i.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + sampleTemplatesDirectory.childFile('widgets.$i.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/widgets.dart';"); + } + + buildKnownCrossImportExamplesFiles(); + bool? success; + final String result = await capture(() async { + success = checker.check(); + }); + expect(result, equals('')); + expect(success, isTrue); + }); + + test('examples/api/lib/sample_templates templates produce violations when invalid', () async { + final Directory sampleTemplatesDirectory = checker.examplesDirectory + .childDirectory('api') + .childDirectory('lib') + .childDirectory('sample_templates'); + + sampleTemplatesDirectory.childFile('cupertino.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + sampleTemplatesDirectory.childFile('material.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + sampleTemplatesDirectory.childFile('widgets.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + + buildKnownCrossImportExamplesFiles(); + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/lib/sample_templates has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ examples/api/lib/sample_templates/cupertino.0.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + '╔═╡ERROR #2╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/lib/sample_templates has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ examples/api/lib/sample_templates/material.0.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + '╔═╡ERROR #3╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/lib/sample_templates has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ examples/api/lib/sample_templates/widgets.0.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('examples/api/test/sample_templates templates produce no violations when valid', () async { + final Directory sampleTemplatesDirectory = checker.examplesDirectory + .childDirectory('api') + .childDirectory('test') + .childDirectory('sample_templates'); + + for (final i in [0, 1, 2]) { + sampleTemplatesDirectory.childFile('cupertino.${i}_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + sampleTemplatesDirectory.childFile('material.${i}_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + sampleTemplatesDirectory.childFile('widgets.${i}_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/widgets.dart';"); + } + + buildKnownCrossImportExamplesFiles(); + bool? success; + final String result = await capture(() async { + success = checker.check(); + }); + expect(result, equals('')); + expect(success, isTrue); + }); + + test('examples/api/test/sample_templates templates produce violations when invalid', () async { + final Directory sampleTemplatesDirectory = checker.examplesDirectory + .childDirectory('api') + .childDirectory('test') + .childDirectory('sample_templates'); + + sampleTemplatesDirectory.childFile('cupertino.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + sampleTemplatesDirectory.childFile('material.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + sampleTemplatesDirectory.childFile('widgets.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/cupertino.dart';"); + + buildKnownCrossImportExamplesFiles(); + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/test/sample_templates has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ examples/api/test/sample_templates/cupertino.0_test.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + '╔═╡ERROR #2╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/test/sample_templates has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ examples/api/test/sample_templates/material.0_test.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + '╔═╡ERROR #3╞════════════════════════════════════════════════════════════════════', + '║ The following file in examples/api/test/sample_templates has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ examples/api/test/sample_templates/widgets.0_test.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + for (final String libraryName in crossImportsGenericExamplesTestCases) { + // The examples root (examples/) is expected to not contain examples. + final bool hasNoCrossImports = + isExamplesRoot(libraryName) || hasNoKnownCrossImports(libraryName); + + test( + 'when not all $libraryName knowns have cross imports', + () async { + final String excludedSample = getFirstCrossImportForLibrary(libraryName); + + buildKnownCrossImportExamplesFiles(excludes: {excludedSample}); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ Huzzah! The following files in $libraryName no longer contain cross imports!', + '║ $excludedSample', + '║ However, they now need to be removed from the', + '║ knownExamplesCrossImports list in the script /dev/bots/check_examples_cross_imports.dart.', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, + skip: hasNoCrossImports, // [intended]: Nothing to log if there are no known imports + ); + + test('unknown $libraryName cross import of Material', () async { + final dartFile = '$libraryName/foo.dart'; + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({dartFile}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ $dartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('multiple unknown $libraryName cross imports of Material', () async { + final dartFileOne = '$libraryName/foo.dart'; + final dartFileTwo = '$libraryName/bar.dart'; + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({dartFileOne, dartFileTwo}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following 2 files in $libraryName have a disallowed import of Material. Refactor them or move them to the Material examples.', + '║ $dartFileOne', + '║ $dartFileTwo', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('unknown $libraryName cross import of Material in test file', () async { + final testDartFile = '$libraryName/foo_test.dart'; + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({testDartFile}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ $testDartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('unknown $libraryName cross import of Cupertino', () async { + final dartFile = '$libraryName/foo.dart'; + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {dartFile}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ $dartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('multiple unknown $libraryName cross imports of Cupertino', () async { + final dartFileOne = '$libraryName/foo.dart'; + final dartFileTwo = '$libraryName/bar.dart'; + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {dartFileOne, dartFileTwo}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following 2 files in $libraryName have a disallowed import of Cupertino. Refactor them or move them to the Cupertino examples.', + '║ $dartFileOne', + '║ $dartFileTwo', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('unknown $libraryName cross import of Cupertino in test file', () async { + final testDartFile = '$libraryName/foo_test.dart'; + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {testDartFile}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ $testDartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + + test('files under $libraryName/build are ignored', () async { + buildKnownCrossImportExamplesFiles(); + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + final Directory buildDirectory = examplesFilesDirectory.childDirectory('build')..createSync(); + buildDirectory.childFile('foo.dart') + ..createSync() + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('files under $libraryName/.dart_tool are ignored', () async { + buildKnownCrossImportExamplesFiles(); + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + final Directory dartToolDirectory = examplesFilesDirectory.childDirectory('.dart_tool') + ..createSync(); + dartToolDirectory.childFile('foo.dart') + ..createSync() + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('files in $libraryName subdirectories are checked', () async { + buildKnownCrossImportExamplesFiles(); + + final Directory examplesFilesDirectory = checkerDirectories.examplesFilesDirectoryFor( + libraryName, + checker.examplesDirectory, + ); + + examplesFilesDirectory.childDirectory('baz').childDirectory('bar').childFile('foo.dart') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ $libraryName/baz/bar/foo.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isExamplesRoot(libraryName)); // [intended]: The root directory should not have examples + } + + for (final String libraryName in crossImportsExamplesApiTestCases) { + // This flag is defined here, instead of near the `skip:` below, + // due to a formatter bug with comments and named arguments, + // which causes `dev/bots/analyze.dart` to fail, since the skip test comment gets put on the next line. + // TODO(navaronbracke): Remove this when https://github.com/dart-lang/dart_style/pull/1848 rolls into Flutter + final bool noCrossImports = hasNoKnownCrossImports(libraryName); + + test('non-Dart files are ignored in $libraryName', () async { + buildKnownCrossImportExamplesFiles(); + + final Directory directory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + directory.childFile('README.md') + ..createSync() + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test('non-Dart files are ignored in $libraryName subdirectories', () async { + buildKnownCrossImportExamplesFiles(); + + final Directory directory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + directory.childDirectory('layers').childDirectory('rendering').childFile('README.md') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }); + + test( + 'non-Dart files with .dart in the filename are ignored $libraryName subdirectories', + () async { + buildKnownCrossImportExamplesFiles(); + + final Directory directory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + directory.childDirectory('layers').childDirectory('rendering').childFile('foo.dart.md') + ..createSync(recursive: true) + ..writeAsStringSync("import 'package:flutter/material.dart';"); + + expect(checker.check(), isTrue); + }, + ); + + test( + 'when not all $libraryName knowns have cross imports', + () async { + final String excludedSample = getFirstCrossImportForLibrary(libraryName); + + buildKnownCrossImportExamplesFiles(excludes: {excludedSample}); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ Huzzah! The following files in $libraryName no longer contain cross imports!', + '║ $excludedSample', + '║ However, they now need to be removed from the', + '║ knownExamplesCrossImports list in the script /dev/bots/check_examples_cross_imports.dart.', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, + skip: noCrossImports, // [intended]: Nothing to log if there are no known imports. + ); + + test('unknown $libraryName cross import of Material', () async { + final dartFile = '$libraryName/foo.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({dartFile}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ $dartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isMaterialExample(libraryName)); // [intended]: Material examples can import Material + + test('multiple unknown $libraryName cross imports of Material', () async { + final dartFileOne = '$libraryName/foo.dart'; + final dartFileTwo = '$libraryName/bar.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({dartFileOne, dartFileTwo}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following 2 files in $libraryName have a disallowed import of Material. Refactor them or move them to the Material examples.', + '║ $dartFileOne', + '║ $dartFileTwo', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isMaterialExample(libraryName)); // [intended]: Material examples can import Material + + test('unknown $libraryName cross import of Material in test file', () async { + final testDartFile = '$libraryName/foo_test.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles({testDartFile}, inDirectory: examplesFilesDirectory); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Material. Refactor it or move it to the Material examples.', + '║ $testDartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isMaterialExample(libraryName)); // [intended]: Material examples can import Material + + test('unknown $libraryName cross import of Cupertino', () async { + final dartFile = '$libraryName/foo.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {dartFile}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ $dartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isCupertinoExample(libraryName)); // [intended]: Cupertino examples can import Cupertino + + test('multiple unknown $libraryName cross imports of Cupertino', () async { + final dartFileOne = '$libraryName/foo.dart'; + final dartFileTwo = '$libraryName/bar.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {dartFileOne, dartFileTwo}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following 2 files in $libraryName have a disallowed import of Cupertino. Refactor them or move them to the Cupertino examples.', + '║ $dartFileOne', + '║ $dartFileTwo', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isCupertinoExample(libraryName)); // [intended]: Cupertino examples can import Cupertino + + test('unknown $libraryName cross import of Cupertino in test file', () async { + final testDartFile = '$libraryName/foo_test.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {testDartFile}, + inDirectory: examplesFilesDirectory, + importString: "import 'package:flutter/cupertino.dart';", + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of Cupertino. Refactor it or move it to the Cupertino examples.', + '║ $testDartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }, skip: isCupertinoExample(libraryName)); // [intended]: Cupertino examples can import Cupertino + + test('files in $libraryName subdirectories are checked', () async { + final dartFile = '$libraryName/baz/bar/foo.dart'; + + final Directory examplesFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + libraryName, + flutterRoot: checker.flutterRoot, + ); + + final LibraryCrossImportStatementType importStatement = + getCrossImportStatementForExamplesLibraryFile(dartFile); + final String disallowedImportName = importStatement.readableName; + + buildKnownCrossImportExamplesFiles(); + writeImportInFiles( + {dartFile}, + inDirectory: examplesFilesDirectory, + importString: importStatement.importString, + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }, shouldHaveErrors: true); + + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following file in $libraryName has a disallowed import of $disallowedImportName. Refactor it or move it to the $disallowedImportName examples.', + '║ $dartFile', + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, isFalse); + }); + } + + test('Material API examples in examples/api are ignored explicitly', () async { + // The Material examples in examples/api have been moved to packages/material_ui. + // The original code is still present in examples/api until it can be finally deleted from there. + // For this reason the checker should ignore the Material examples in examples/api, + // even if they contain cross imports. + + buildKnownCrossImportExamplesFiles(); + + const examplesApiMaterialLibraryName = 'examples/api/lib/material'; + const examplesApiMaterialTestLibraryName = 'examples/api/test/material'; + const LibraryCrossImportStatementType importStatement = .cupertino; + + final Directory examplesMaterialLibFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + examplesApiMaterialLibraryName, + flutterRoot: checker.flutterRoot, + ); + + final Directory examplesMaterialTestFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + examplesApiMaterialTestLibraryName, + flutterRoot: checker.flutterRoot, + ); + + writeImportInFiles( + {'examples/api/lib/material/qux.dart', 'examples/api/lib/material/baz/foo.dart'}, + inDirectory: examplesMaterialLibFilesDirectory, + importString: importStatement.importString, + ); + + writeImportInFiles( + {'examples/api/test/material/qux_test.dart', 'examples/api/test/material/baz/foo_test.dart'}, + inDirectory: examplesMaterialTestFilesDirectory, + importString: importStatement.importString, + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }); + expect(result, equals('')); + expect(success, isTrue); + }); + + test('Cupertino API examples in examples/api are ignored explicitly', () async { + // The Cupertino examples in examples/api have been moved to packages/cupertino_ui. + // The original code is still present in examples/api until it can be finally deleted from there. + // For this reason the checker should ignore the Cupertino examples in examples/api, + // even if they contain cross imports. + + buildKnownCrossImportExamplesFiles(); + + const examplesApiCupertinoLibraryName = 'examples/api/lib/cupertino'; + const examplesApiCupertinoTestLibraryName = 'examples/api/test/cupertino'; + const LibraryCrossImportStatementType importStatement = .material; + + final Directory examplesCupertinoLibFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + examplesApiCupertinoLibraryName, + flutterRoot: checker.flutterRoot, + ); + + final Directory examplesCupertinoTestFilesDirectory = getDirectoryForExamplesSlashApiLibrary( + examplesApiCupertinoTestLibraryName, + flutterRoot: checker.flutterRoot, + ); + + writeImportInFiles( + {'examples/api/lib/cupertino/qux.dart', 'examples/api/lib/cupertino/baz/foo.dart'}, + inDirectory: examplesCupertinoLibFilesDirectory, + importString: importStatement.importString, + ); + + writeImportInFiles( + { + 'examples/api/test/cupertino/qux_test.dart', + 'examples/api/test/cupertino/baz/foo_test.dart', + }, + inDirectory: examplesCupertinoTestFilesDirectory, + importString: importStatement.importString, + ); + + bool? success; + final String result = await capture(() async { + success = checker.check(); + }); + expect(result, equals('')); + expect(success, isTrue); + }); +} + +/// Get a [LibraryCrossImportStatementType], for the file at the given [filePath], +/// that will result in the [ExamplesCrossImportChecker] flagging the file as having disallowed cross imports. +/// +/// Returns [LibraryCrossImportStatementType.cupertino] if the file is in the Material examples, +/// and [LibraryCrossImportStatementType.material] if the file is in the Cupertino examples. +/// +/// Returns [LibraryCrossImportStatementType.material] for any other library, +/// such as `examples/layers/rendering/spinning_square.dart`. +LibraryCrossImportStatementType getCrossImportStatementForExamplesLibraryFile(String filePath) { + if (filePath.startsWith('examples/api/lib/material/') || + filePath.startsWith('examples/api/test/material/')) { + return LibraryCrossImportStatementType.cupertino; + } + + if (filePath.startsWith('examples/api/lib/cupertino/') || + filePath.startsWith('examples/api/test/cupertino/')) { + return LibraryCrossImportStatementType.material; + } + + return LibraryCrossImportStatementType.material; +} + +/// Get the directory for the given `examples/api` [libraryName]. +/// +/// The library name can only contain lowercase a-z and underscores +/// and must start with either `examples/api/lib` or `examples/api/test`. +Directory getDirectoryForExamplesSlashApiLibrary( + String libraryName, { + required Directory flutterRoot, +}) { + if (!_kExamplesSlashApiLibraryPattern.hasMatch(libraryName)) { + throw ArgumentError('Invalid library name: $libraryName', 'libraryName'); + } + + final String fullPath = path.joinAll([flutterRoot.path, ...libraryName.split('/')]); + + return flutterRoot.fileSystem.directory(fullPath); +} + +/// Get the first known cross import for the given [libraryName]. +/// +/// Throws a [StateError] if there are no known cross imports for the given library. +String getFirstCrossImportForLibrary(String libraryName) { + // Use the first entry that belongs to this library, since known imports sets + // are shared between lib/ and test/ directories and may contain paths for both. + return ExamplesCrossImportChecker.knownExamplesCrossImports.firstWhere( + (String p) => p.startsWith('$libraryName/'), + ); +} + +/// Returns whether there are no known cross imports for the given [libraryName]. +bool hasNoKnownCrossImports(String libraryName) { + return !ExamplesCrossImportChecker.knownExamplesCrossImports.any( + (String entry) => entry.startsWith('$libraryName/'), + ); +} + +/// Returns whether the given [libraryName] matches the Material examples under `examples/api`. +bool isMaterialExample(String libraryName) { + return libraryName == 'examples/api/lib/material' || libraryName == 'examples/api/test/material'; +} + +/// Returns whether the given [libraryName] matches the Cupertino examples under `examples/api`. +bool isCupertinoExample(String libraryName) { + return libraryName == 'examples/api/lib/cupertino' || + libraryName == 'examples/api/test/cupertino'; +} + +/// Returns whether the given [libraryName] matches the root `examples` or `examples/api` directories, +/// which contain subdirectories with examples, but should themselves be void of examples. +bool isExamplesRoot(String libraryName) { + return libraryName == 'examples' || libraryName == 'examples/api'; +} + +// A utility that keeps track of the directories under test, +// to avoid having to late initialize them individually in `setUp()`. +class _CrossImportsExamplesDirectories { + factory _CrossImportsExamplesDirectories(Directory examplesDirectory) { + return _CrossImportsExamplesDirectories._( + examplesSlashApiDirectory: examplesDirectory.childDirectory('api'), + examplesFlutterViewDirectory: examplesDirectory.childDirectory('flutter_view'), + examplesHelloWorldDirectory: examplesDirectory.childDirectory('hello_world'), + examplesImageListDirectory: examplesDirectory.childDirectory('image_list'), + examplesLayersDirectory: examplesDirectory.childDirectory('layers'), + examplesMultipleWindowsDirectory: examplesDirectory.childDirectory('multiple_windows'), + examplesPlatformChannelDirectory: examplesDirectory.childDirectory('platform_channel'), + examplesPlatformChannelSwiftDirectory: examplesDirectory.childDirectory( + 'platform_channel_swift', + ), + examplesPlatformViewDirectory: examplesDirectory.childDirectory('platform_view'), + examplesSplashDirectory: examplesDirectory.childDirectory('splash'), + examplesTextureDirectory: examplesDirectory.childDirectory('texture'), + ); + } + + const _CrossImportsExamplesDirectories._({ + required this.examplesSlashApiDirectory, + required this.examplesFlutterViewDirectory, + required this.examplesHelloWorldDirectory, + required this.examplesImageListDirectory, + required this.examplesLayersDirectory, + required this.examplesMultipleWindowsDirectory, + required this.examplesPlatformChannelDirectory, + required this.examplesPlatformChannelSwiftDirectory, + required this.examplesPlatformViewDirectory, + required this.examplesSplashDirectory, + required this.examplesTextureDirectory, + }); + + final Directory examplesSlashApiDirectory; + final Directory examplesFlutterViewDirectory; + final Directory examplesHelloWorldDirectory; + final Directory examplesImageListDirectory; + final Directory examplesLayersDirectory; + final Directory examplesMultipleWindowsDirectory; + final Directory examplesPlatformChannelDirectory; + final Directory examplesPlatformChannelSwiftDirectory; + final Directory examplesPlatformViewDirectory; + final Directory examplesSplashDirectory; + final Directory examplesTextureDirectory; + + /// A mapping of `examples/xyz` directories + /// to their corresponding known imports list in `check_examples_cross_imports.dart` + Map> getKnownFiles(Directory examplesDirectory) { + final Directory libDirectory = examplesSlashApiDirectory.childDirectory('lib'); + final Directory testDirectory = examplesSlashApiDirectory.childDirectory('test'); + final Map> exampleSlashApiSubdirectoryMapping = {}; + + for (final directory in [libDirectory, testDirectory]) { + exampleSlashApiSubdirectoryMapping[directory.childDirectory('animation')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('foundation')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('gestures')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('painting')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('rendering')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('sample_templates')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('services')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('ui')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + exampleSlashApiSubdirectoryMapping[directory.childDirectory('widgets')] = + ExamplesCrossImportChecker.knownExamplesCrossImports; + } + + return >{ + examplesDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesSlashApiDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + ...exampleSlashApiSubdirectoryMapping, + examplesFlutterViewDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesHelloWorldDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesImageListDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesLayersDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesMultipleWindowsDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesPlatformChannelDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesPlatformChannelSwiftDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesPlatformViewDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesSplashDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + examplesTextureDirectory: ExamplesCrossImportChecker.knownExamplesCrossImports, + }; + } + + /// Create the `examples/xyz` directories for the test cases, excluding `examples/api` subdirectories. + void createExamplesDirectories(Directory examplesDirectory) { + final Map> knownFiles = getKnownFiles(examplesDirectory); + + for (final Directory directory in knownFiles.keys) { + // The `examples` directory is created in `setUp()`. + if (directory == examplesDirectory) { + continue; + } + + directory.createSync(recursive: true); + } + } + + /// Get the examples directory for the given [libraryName]. + Directory examplesFilesDirectoryFor(String libraryName, Directory examplesDirectory) { + const unsupportedPrefix = 'examples/api'; + + if (libraryName.startsWith(unsupportedPrefix) && + libraryName.length > unsupportedPrefix.length) { + throw ArgumentError( + 'For $libraryName, use getDirectoryForExamplesSlashApiLibrary(libraryName) instead, ' + 'which supports getting directories for examples/api lib and test subdirectories.', + ); + } + + return switch (libraryName) { + 'examples' => examplesDirectory, + 'examples/api' => examplesSlashApiDirectory, + 'examples/flutter_view' => examplesFlutterViewDirectory, + 'examples/hello_world' => examplesHelloWorldDirectory, + 'examples/image_list' => examplesImageListDirectory, + 'examples/layers' => examplesLayersDirectory, + 'examples/multiple_windows' => examplesMultipleWindowsDirectory, + 'examples/platform_channel' => examplesPlatformChannelDirectory, + 'examples/platform_channel_swift' => examplesPlatformChannelSwiftDirectory, + 'examples/platform_view' => examplesPlatformViewDirectory, + 'examples/splash' => examplesSplashDirectory, + 'examples/texture' => examplesTextureDirectory, + _ => throw ArgumentError('Unknown library name: $libraryName'), + }; + } +} + +// A mapping of `examples/**` test cases for the cross imports checker, excluding `examples/api/**`. +const crossImportsGenericExamplesTestCases = [ + 'examples', + 'examples/api', + 'examples/flutter_view', + 'examples/hello_world', + 'examples/image_list', + 'examples/layers', + 'examples/multiple_windows', + 'examples/platform_channel', + 'examples/platform_channel_swift', + 'examples/platform_view', + 'examples/splash', + 'examples/texture', +]; + +// A mapping of `examples/api/lib/**` and `examples/api/test/**` test cases for the cross imports checker, +// excluding `examples/api/lib/sample_templates` and `examples/api/test/sample_templates`. +const crossImportsExamplesApiTestCases = [ + 'examples/api/lib/animation', + 'examples/api/lib/foundation', + 'examples/api/lib/gestures', + 'examples/api/lib/painting', + 'examples/api/lib/rendering', + 'examples/api/lib/services', + 'examples/api/lib/ui', + 'examples/api/lib/widgets', + 'examples/api/test/animation', + 'examples/api/test/foundation', + 'examples/api/test/gestures', + 'examples/api/test/painting', + 'examples/api/test/rendering', + 'examples/api/test/services', + 'examples/api/test/ui', + 'examples/api/test/widgets', +]; diff --git a/dev/bots/test/check_tests_cross_imports_test.dart b/dev/bots/test/check_tests_cross_imports_test.dart index fe5a1ca4c7476..a6d1c287230bd 100644 --- a/dev/bots/test/check_tests_cross_imports_test.dart +++ b/dev/bots/test/check_tests_cross_imports_test.dart @@ -9,8 +9,8 @@ import 'package:file/memory.dart'; import 'package:path/path.dart' as path; import '../check_tests_cross_imports.dart'; -import '../utils.dart'; import 'common.dart'; +import 'cross_imports_checker_test_utils.dart'; void main() { late TestsCrossImportChecker checker; @@ -136,7 +136,7 @@ void main() { }, shouldHaveErrors: true); final String lines = [ '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', - '║ Huzzah! The following tests in $libraryName no longer contain cross imports!', + '║ Huzzah! The following files in $libraryName no longer contain cross imports!', '║ $excludedSample', '║ However, they now need to be removed from the', '║ $knownCrossImportsListName list in the script /dev/bots/check_tests_cross_imports.dart.', @@ -332,79 +332,9 @@ void main() { } } -typedef AsyncVoidCallback = Future Function(); - -Future capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async { - final buffer = StringBuffer(); - final PrintCallback oldPrint = print; - try { - print = (Object? line) { - buffer.writeln(line); - }; - await callback(); - expect( - hasError, - shouldHaveErrors, - reason: buffer.isEmpty - ? '(No output to report.)' - : hasError - ? 'Unexpected errors:\n$buffer' - : 'Unexpected success:\n$buffer', - ); - } finally { - print = oldPrint; - resetErrorStatus(); - } - if (stdout.supportsAnsiEscapes) { - // Remove ANSI escapes when this test is running on a terminal. - return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), ''); - } else { - return buffer.toString(); - } -} - /// Returns whether the given [libraryName] matches the Cupertino library under `flutter/test`. bool isCupertino(String libraryName) => libraryName == 'packages/flutter/test/cupertino'; -File getFile(String filepath, Directory directory) { - final String platformFilepath = filepath.replaceAll('/', Platform.pathSeparator); - final String searchPattern = directory.basename + Platform.pathSeparator; - // Don't use `lastIndexOf`, as for files in test fixes - // i.e. `packages/flutter_test/test_fixes/flutter_test/matchers.dart` - // the overlap index could appear multiple times. - // Only take the first one. - final int overlapIndex = platformFilepath.indexOf(searchPattern); - - if (overlapIndex < 0) { - throw ArgumentError('filepath $filepath must be located in directory ${directory.path}.'); - } - - final String filename = platformFilepath.substring(overlapIndex + searchPattern.length); - return directory.childFile(filename); -} - -/// Writes [importString] into the given file. -/// -/// The default [importString] is `import 'package:flutter/material.dart';`. -void writeImport(File file, [String importString = "import 'package:flutter/material.dart';"]) { - file - ..createSync(recursive: true) - ..writeAsStringSync(importString); -} - -/// Writes [importString] into the given [filePaths] in [inDirectory]. -/// -/// The default [importString] is `import 'package:flutter/material.dart';`. -void writeImportInFiles( - Iterable filePaths, { - required Directory inDirectory, - String importString = "import 'package:flutter/material.dart';", -}) { - for (final filepath in filePaths) { - writeImport(getFile(filepath, inDirectory), importString); - } -} - // A utility that keeps track of the directories under test, // to avoid having to late initialize them individually in `setUp()`. class _CrossImportsTestDirectories { diff --git a/dev/bots/test/cross_imports_checker_test_utils.dart b/dev/bots/test/cross_imports_checker_test_utils.dart new file mode 100644 index 0000000000000..f6644cca98aaf --- /dev/null +++ b/dev/bots/test/cross_imports_checker_test_utils.dart @@ -0,0 +1,88 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:file/file.dart'; + +import '../utils.dart'; +import 'common.dart'; + +typedef AsyncVoidCallback = Future Function(); + +/// Capture the output of calling [callback] +/// and validate if it emitted any formatted errors. +/// +/// This function will fail the current test if either of the following is true: +/// - [shouldHaveErrors] is true and no errors were reported +/// - [shouldHaveErrors] is false and any errors were reported +/// +/// Returns the captured output. +Future capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async { + final buffer = StringBuffer(); + final PrintCallback oldPrint = print; + try { + print = (Object? line) { + buffer.writeln(line); + }; + await callback(); + expect( + hasError, + shouldHaveErrors, + reason: buffer.isEmpty + ? '(No output to report.)' + : hasError + ? 'Unexpected errors:\n$buffer' + : 'Unexpected success:\n$buffer', + ); + } finally { + print = oldPrint; + resetErrorStatus(); + } + if (stdout.supportsAnsiEscapes) { + // Remove ANSI escapes when this test is running on a terminal. + return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), ''); + } else { + return buffer.toString(); + } +} + +File getFile(String filepath, Directory directory) { + final String platformFilepath = filepath.replaceAll('/', Platform.pathSeparator); + final String searchPattern = directory.basename + Platform.pathSeparator; + // Don't use `lastIndexOf`, as for files in test fixes + // i.e. `packages/flutter_test/test_fixes/flutter_test/matchers.dart` + // the overlap index could appear multiple times. + // Only take the first one. + final int overlapIndex = platformFilepath.indexOf(searchPattern); + + if (overlapIndex < 0) { + throw ArgumentError('filepath $filepath must be located in directory ${directory.path}.'); + } + + final String filename = platformFilepath.substring(overlapIndex + searchPattern.length); + return directory.childFile(filename); +} + +/// Writes [importString] into the given file. +/// +/// The default [importString] is `import 'package:flutter/material.dart';`. +void writeImport(File file, [String importString = "import 'package:flutter/material.dart';"]) { + file + ..createSync(recursive: true) + ..writeAsStringSync(importString); +} + +/// Writes [importString] into the given [filePaths] in [inDirectory]. +/// +/// The default [importString] is `import 'package:flutter/material.dart';`. +void writeImportInFiles( + Iterable filePaths, { + required Directory inDirectory, + String importString = "import 'package:flutter/material.dart';", +}) { + for (final filepath in filePaths) { + writeImport(getFile(filepath, inDirectory), importString); + } +} From ad372d343dab4ff77d57a916f84dafa584917cc5 Mon Sep 17 00:00:00 2001 From: flutter-pub-roller-bot <137456488+flutter-pub-roller-bot@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:42:05 -0700 Subject: [PATCH 109/147] Roll pub packages (#190259) This PR was generated by `flutter update-packages --force-upgrade`. --- packages/flutter/pubspec.yaml | 4 ++-- packages/flutter_test/pubspec.yaml | 4 ++-- pubspec.lock | 4 ++-- pubspec.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 04a6d3fd2679e..cb588fc0192a6 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # See https://github.com/flutter/flutter/issues/185017 material_color_utilities: 0.13.0 meta: ^1.19.0 - vector_math: ^2.4.1 + vector_math: ^2.4.2 sky_engine: sdk: flutter @@ -42,4 +42,4 @@ dev_dependencies: path: any platform: any -# PUBSPEC CHECKSUM: vrhoum +# PUBSPEC CHECKSUM: sb0dvh diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 9bc56569d1fc4..cf6564aa5d8c8 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: stack_trace: ^1.12.1 # Used by globalToLocal et al. - vector_math: ^2.4.1 + vector_math: ^2.4.2 # Used to detect memory leaks with `testWidgets`. leak_tracker_flutter_testing: ^3.0.10 @@ -48,4 +48,4 @@ dev_dependencies: flutter_driver: sdk: flutter -# PUBSPEC CHECKSUM: b83lrh +# PUBSPEC CHECKSUM: 380igp diff --git a/pubspec.lock b/pubspec.lock index 7a6dbdd9e8430..8f82714025121 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1171,10 +1171,10 @@ packages: dependency: "direct main" description: name: vector_math - sha256: "99b6fb0d1881dd1302cd42e0f53bcce99ea246e0a85c8aeb79fb38b283852e88" + sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" video_player: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f4e1a6fe3c88e..3af22515f270b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -190,7 +190,7 @@ dependencies: url_launcher_platform_interface: 2.3.2 url_launcher_web: 2.4.3 url_launcher_windows: 3.1.5 - vector_math: ^2.4.1 + vector_math: ^2.4.2 video_player: 2.13.0 video_player_android: 2.12.0 video_player_avfoundation: 2.11.0 @@ -222,4 +222,4 @@ dependencies: dev_dependencies: ffigen: 20.1.1 -# PUBSPEC CHECKSUM: 1n10pp +# PUBSPEC CHECKSUM: db69ps From 1e927971a0f98ae14a9ad38b45c4024ae7f0d25a Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 29 Jul 2026 21:24:33 -0400 Subject: [PATCH 110/147] Roll Skia from 6fea713d32ec to 78afc18c9ba0 (4 revisions) (#190257) https://skia.googlesource.com/skia.git/+log/6fea713d32ec..78afc18c9ba0 2026-07-29 kjlubick@google.com Fix mismatch with maps in SkRP 2026-07-29 kjlubick@google.com Address incorrect handling of a map pointer in SkRP 2026-07-29 jmbetancourt@google.com Add a global logging handler for Skia clients 2026-07-29 alexisdavidc@google.com [Bug] Replaced reliance on FcFontSort with FcFontMatch If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0baf5043a49d5..3be25c7653fce 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '6fea713d32ec101877b670ce05ff8844265525fc', + 'skia_revision': '78afc18c9ba01a2a6c13d530992241b3a6f82205', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From d2248aaa228bff240d4a8398afc41ee34e36a19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0hsan=20G=C3=B6rgel?= Date: Thu, 30 Jul 2026 06:00:00 +0300 Subject: [PATCH 111/147] Return null from RenderProxyBoxMixin.computeDryBaseline when the child has no baseline (#189723) `RenderProxyBoxMixin.computeDryBaseline` forwards the child's dry baseline as `result ?? super.computeDryBaseline(constraints, baseline)`. When a child is present but has no baseline, the null falls through to `RenderBox.computeDryBaseline`, which asserts that the class "does not implement computeDryBaseline". Returning null is explicitly allowed by the `computeDryBaseline` contract, so any proxy box above such a child crashes when queried for a dry baseline, for example a baseline-aligned `WidgetSpan` wrapping `Semantics`. The mixin now returns the child's answer as-is when a child is present, and only falls back to the base implementation when there is no child. This matches what `RenderConstrainedBox` already does in the same file. Added a rendering-layer regression test. The widget-level reproduction from the issue also passes with this change. Fixes https://github.com/flutter/flutter/issues/189711 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md --------- Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com> --- .../flutter/lib/src/rendering/proxy_box.dart | 6 ++-- .../test/rendering/proxy_box_test.dart | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index a98227645f87a..e47a18c9a668f 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -102,8 +102,10 @@ mixin RenderProxyBoxMixin on RenderBox, RenderObjectWithChi @override @protected double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { - final double? result = child?.getDryBaseline(constraints, baseline); - return result ?? super.computeDryBaseline(constraints, baseline); + final RenderBox? child = this.child; + return child == null + ? super.computeDryBaseline(constraints, baseline) + : child.getDryBaseline(constraints, baseline); } @override diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index 75d728f2f8345..86e7c888082e3 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -1089,6 +1089,23 @@ void main() { expect(backdropFilter.filterConfig, equals(filterConfig1)); expect(() => backdropFilter.filter, throwsAssertionError); }); + + test('RenderProxyBoxMixin.computeDryBaseline returns null when the child has no baseline', () { + // Regression test for https://github.com/flutter/flutter/issues/189711 + final child = _RenderNoBaseline(); + final proxy = RenderSemanticsAnnotations( + child: child, + properties: const SemanticsProperties(), + ); + layout(proxy); + expect( + proxy.getDryBaseline( + const BoxConstraints.tightFor(width: 40.0, height: 20.0), + TextBaseline.alphabetic, + ), + isNull, + ); + }); } class _TestRectClipper extends CustomClipper { @@ -1225,3 +1242,20 @@ class RenderBoxWithTestConstraints extends RenderProxyBox { return constraints.constrain(Size.square(constraints.testValue)); } } + +class _RenderNoBaseline extends RenderBox { + @override + void performLayout() { + size = constraints.constrain(const Size(40.0, 20.0)); + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.constrain(const Size(40.0, 20.0)); + } + + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + return null; + } +} From ddb43a36077c7fa8b61ce2a1aa55e71d008ed480 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Wed, 29 Jul 2026 22:44:13 -0700 Subject: [PATCH 112/147] Revert: Split the platform and cpuArch part of TargetPlatform (#190258) Reverts: [Split the platform and cpuArch part of TargetPlatform](https://github.com/flutter/flutter/pull/189479) Initiated by: @chingjun Reason for reverting: Broke postsubmit test. See https://github.com/flutter/flutter/issues/190252 Original PR Author: @chingjun Reviewed By: @bkonyi The original PR description is provided below: Convert `enum TargetPlatform` into a class with two fields: `TargetPlatformType` and `CpuArch`. Fixes: https://github.com/flutter/flutter/issues/190252 --- .../bin/fuchsia_asset_builder.dart | 2 +- .../lib/src/android/android_device.dart | 41 +- .../lib/src/android/android_emulator.dart | 1 - .../lib/src/android/gradle_utils.dart | 4 +- packages/flutter_tools/lib/src/artifacts.dart | 178 ++--- packages/flutter_tools/lib/src/asset.dart | 14 +- .../flutter_tools/lib/src/base/build.dart | 58 +- packages/flutter_tools/lib/src/base/dds.dart | 3 +- .../flutter_tools/lib/src/build_info.dart | 257 ++++---- .../lib/src/build_system/targets/android.dart | 18 +- .../lib/src/build_system/targets/assets.dart | 2 +- .../lib/src/build_system/targets/common.dart | 56 +- .../targets/dart_plugin_registrant.dart | 4 +- .../targets/icon_tree_shaker.dart | 4 +- .../lib/src/build_system/targets/ios.dart | 21 +- .../lib/src/build_system/targets/macos.dart | 35 +- .../build_system/targets/native_assets.dart | 4 +- .../lib/src/build_system/targets/web.dart | 12 +- .../build_system/tools/shader_compiler.dart | 30 +- .../lib/src/commands/assemble.dart | 38 +- .../lib/src/commands/build_bundle.dart | 28 +- .../lib/src/commands/build_ios.dart | 7 +- .../lib/src/commands/build_ios_framework.dart | 4 +- .../lib/src/commands/build_linux.dart | 6 +- .../src/commands/build_macos_framework.dart | 4 +- .../lib/src/commands/daemon.dart | 24 +- .../flutter_tools/lib/src/commands/run.dart | 21 +- .../flutter_tools/lib/src/commands/test.dart | 6 +- packages/flutter_tools/lib/src/compile.dart | 14 +- .../lib/src/custom_devices/custom_device.dart | 7 +- .../custom_devices/custom_device_config.dart | 10 +- .../flutter_tools/lib/src/darwin/darwin.dart | 9 +- packages/flutter_tools/lib/src/device.dart | 33 +- packages/flutter_tools/lib/src/emulator.dart | 1 - .../lib/src/flutter_application_package.dart | 28 +- .../flutter_tools/lib/src/ios/devices.dart | 3 + .../lib/src/ios/ios_emulators.dart | 1 - packages/flutter_tools/lib/src/ios/mac.dart | 4 +- .../flutter_tools/lib/src/ios/simulators.dart | 3 + .../lib/src/ios/xcode_build_settings.dart | 9 +- .../lib/src/isolated/devfs_web.dart | 6 +- .../isolated/native_assets/native_assets.dart | 45 +- .../src/isolated/native_assets/targets.dart | 77 ++- .../native_assets/test/native_assets.dart | 6 +- .../lib/src/isolated/resident_web_runner.dart | 12 +- .../lib/src/isolated/web_asset_server.dart | 2 +- .../lib/src/linux/build_linux.dart | 6 +- .../lib/src/linux/linux_device.dart | 10 + .../lib/src/macos/macos_device.dart | 3 + .../lib/src/macos/macos_ipad_device.dart | 3 + .../flutter_tools/lib/src/mdns_discovery.dart | 28 +- .../lib/src/proxied_devices/devices.dart | 2 +- .../lib/src/resident_runner.dart | 47 +- packages/flutter_tools/lib/src/run_hot.dart | 7 +- .../lib/src/runner/flutter_command.dart | 28 +- .../lib/src/test/flutter_web_platform.dart | 6 +- .../flutter_tools/lib/src/test/runner.dart | 2 +- .../lib/src/test/test_compiler.dart | 2 +- .../lib/src/test/web_test_compiler.dart | 4 +- .../lib/src/tester/flutter_tester.dart | 4 +- .../flutter_tools/lib/src/web/web_device.dart | 6 + .../lib/src/windows/build_windows.dart | 10 +- .../lib/src/windows/windows_device.dart | 7 +- .../commands.shard/hermetic/attach_test.dart | 6 +- .../hermetic/build_darwin_framework_test.dart | 4 +- .../hermetic/build_swift_package_test.dart | 18 +- .../hermetic/build_windows_test.dart | 2 +- .../commands.shard/hermetic/daemon_test.dart | 7 +- .../commands.shard/hermetic/doctor_test.dart | 607 ++++++++++-------- .../commands.shard/hermetic/drive_test.dart | 6 +- .../commands.shard/hermetic/install_test.dart | 4 +- .../hermetic/proxied_devices_test.dart | 4 +- .../commands.shard/hermetic/run_test.dart | 24 +- .../hermetic/screenshot_command_test.dart | 79 +-- .../widget_preview/widget_preview_test.dart | 6 +- .../android/android_device_start_test.dart | 6 +- .../android/android_device_test.dart | 8 +- .../android/android_emulator_test.dart | 2 +- .../application_package_test.dart | 15 +- .../test/general.shard/artifacts_test.dart | 109 ++-- .../asset_bundle_flavors_test.dart | 2 +- .../asset_bundle_package_fonts_test.dart | 4 +- .../asset_bundle_package_test.dart | 12 +- .../test/general.shard/asset_bundle_test.dart | 76 +-- .../asset_bundle_variant_test.dart | 10 +- .../test/general.shard/asset_test.dart | 14 +- .../test/general.shard/base/build_test.dart | 59 +- .../test/general.shard/build_info_test.dart | 52 +- .../build_system/source_test.dart | 4 +- .../build_system/targets/android_test.dart | 21 +- .../build_system/targets/assets_test.dart | 6 +- .../build_system/targets/common_test.dart | 56 +- .../targets/deferred_components_test.dart | 6 +- .../targets/icon_tree_shaker_test.dart | 34 +- .../build_system/targets/ios_test.dart | 12 +- .../build_system/targets/linux_test.dart | 67 +- .../build_system/targets/macos_test.dart | 18 +- .../targets/shader_compiler_test.dart | 42 +- .../targets/web_dry_run_test.dart | 2 +- .../build_system/targets/web_test.dart | 4 +- .../build_system/targets/windows_test.dart | 24 +- .../general.shard/bundle_builder_test.dart | 12 +- .../test/general.shard/cold_test.dart | 4 +- .../compile_expression_test.dart | 2 +- .../test/general.shard/compile_test.dart | 12 +- .../custom_devices/custom_device_test.dart | 8 +- .../test/general.shard/darwin_test.dart | 14 +- .../general.shard/desktop_device_test.dart | 4 +- .../test/general.shard/device_test.dart | 4 +- .../drive/drive_service_test.dart | 2 +- .../drive/web_driver_service_test.dart | 2 +- .../test/general.shard/emulator_test.dart | 1 - .../general.shard/flutter_platform_test.dart | 6 +- .../test/general.shard/hot_shared.dart | 4 +- .../test/general.shard/hot_test.dart | 4 +- .../integration_test_device_test.dart | 2 +- .../isolated/android/native_assets_test.dart | 8 +- .../targets/native_assets_test.dart | 4 +- .../isolated/data_assets_test.dart | 4 +- .../isolated/ios/native_assets_test.dart | 4 +- .../isolated/linux/native_assets_test.dart | 14 +- .../isolated/macos/native_assets_test.dart | 8 +- .../isolated/native_assets_test.dart | 20 +- .../isolated/resident_runner_test.dart | 7 +- .../isolated/windows/native_assets_test.dart | 6 +- .../linux/linux_device_test.dart | 4 +- .../macos/macos_device_test.dart | 2 +- .../macos/macos_ipad_device_test.dart | 2 +- .../general.shard/mdns_discovery_test.dart | 2 +- .../resident_runner_helpers.dart | 13 +- .../general.shard/resident_runner_test.dart | 36 +- .../resident_web_runner_test.dart | 2 +- .../runner/target_devices_test.dart | 10 +- .../general.shard/terminal_handler_test.dart | 6 +- .../test/web_test_compiler_test.dart | 6 +- .../tester/flutter_tester_test.dart | 2 +- .../windows/build_windows_flavor_test.dart | 11 +- .../windows/windows_device_test.dart | 2 +- .../shader_compiler_test.dart | 6 +- .../web_plugin_registrant_test.dart | 4 +- .../flutter_tools/test/src/fake_devices.dart | 8 +- 141 files changed, 1596 insertions(+), 1435 deletions(-) diff --git a/packages/flutter_tools/bin/fuchsia_asset_builder.dart b/packages/flutter_tools/bin/fuchsia_asset_builder.dart index 1aaa82d1cc35d..d6ad78f49a6fb 100644 --- a/packages/flutter_tools/bin/fuchsia_asset_builder.dart +++ b/packages/flutter_tools/bin/fuchsia_asset_builder.dart @@ -66,7 +66,7 @@ Future run(List args) async { packageConfigPath: argResults[_kOptionPackages] as String? ?? findPackageConfigFileOrDefault(globals.fs.currentDirectory).path, - targetPlatform: const TargetPlatform(.fuchsia, .arm64), // This is not arch specific. + targetPlatform: TargetPlatform.fuchsia_arm64, // This is not arch specific. ); if (assets == null) { diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 0535322a6b113..29cf0aef59a5d 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -190,6 +190,16 @@ class AndroidDevice extends Device { } } + @override + late final Future targetPlatform = () async { + return switch (await cpuArch) { + CpuArch.arm64 => TargetPlatform.android_arm64, + CpuArch.armv7 => TargetPlatform.android_arm, + CpuArch.x64 => TargetPlatform.android_x64, + CpuArch.x86 || CpuArch.riscv64 || CpuArch.unknown => TargetPlatform.unsupported, + }; + }(); + @override late final Future cpuArch = () async { // http://developer.android.com/ndk/guides/abis.html (x86, armeabi-v7a, ...) @@ -217,7 +227,26 @@ class AndroidDevice extends Device { @override Future supportsRuntimeMode(BuildMode buildMode) async { - return buildMode != .jitRelease; + switch (await targetPlatform) { + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + return buildMode != BuildMode.jitRelease; + case TargetPlatform.android: + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: + case TargetPlatform.unsupported: + throw UnsupportedError('Invalid target platform for Android'); + } } @override @@ -844,9 +873,13 @@ class AndroidDevice extends Device { @override Future isSupported() async { - return switch (await cpuArch) { - .arm64 || .armv7 || .x64 => true, - .x86 || .riscv64 || .unknown => false, + final TargetPlatform platform = await targetPlatform; + return switch (platform) { + TargetPlatform.android || + TargetPlatform.android_arm || + TargetPlatform.android_arm64 || + TargetPlatform.android_x64 => true, + _ => false, }; } diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart index c72d3a7b33e99..b8938b5e8c531 100644 --- a/packages/flutter_tools/lib/src/android/android_emulator.dart +++ b/packages/flutter_tools/lib/src/android/android_emulator.dart @@ -13,7 +13,6 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/process.dart'; import '../base/utils.dart'; -import '../build_info.dart'; import '../device.dart'; import '../emulator.dart'; import 'android_sdk.dart'; diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 16fa52fd3210a..770e287a4d2f4 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -1191,13 +1191,13 @@ void updateLocalProperties({ if (buildInfo != null) { changeIfNecessary('flutter.buildMode', buildInfo.modeName); final String? buildName = validatedBuildNameForPlatform( - PlatformType.android, + TargetPlatform.android_arm, buildInfo.buildName ?? project.manifest.buildName, globals.logger, ); changeIfNecessary('flutter.versionName', buildName); final String? buildNumber = validatedBuildNumberForPlatform( - PlatformType.android, + TargetPlatform.android_arm, buildInfo.buildNumber ?? project.manifest.buildNumber, globals.logger, ); diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index e9ee816f28537..2c4d4b731b535 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -197,18 +197,37 @@ enum HostArtifact { // TODO(knopp): Remove once darwin artifacts are universal and moved out of darwin-x64 String _enginePlatformDirectoryName(TargetPlatform platform) { - if (platform.type == .macos) { + if (platform == TargetPlatform.darwin) { return 'darwin-x64'; } - // iOS engine artifacts live in a single `ios` directory regardless of the - // target architecture, so the arch-qualified name from [getName] is not used - // here. - if (platform.type == .ios) { - return 'ios'; - } return platform.getName(); } +// Remove android target platform type. +TargetPlatform? _mapTargetPlatform(TargetPlatform? targetPlatform) { + switch (targetPlatform) { + case TargetPlatform.android: + return TargetPlatform.android_arm64; + case TargetPlatform.ios: + case TargetPlatform.darwin: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.unsupported: + case null: + return targetPlatform; + } +} + class EngineBuildPaths { const EngineBuildPaths({ required this.targetEngine, @@ -402,34 +421,35 @@ class CachedArtifacts implements Artifacts { BuildMode? mode, EnvironmentType? environmentType, }) { - switch (platform?.type) { - case .android: - // A generic Android target has an unknown CPU architecture (e.g. the - // architecture-independent patched SDK requested when compiling the - // app's Dart kernel). Default to arm64 in that case, matching the - // historical behavior of the removed `_mapTargetPlatform` helper. - final TargetPlatform androidPlatform = platform!.cpuArch == .unknown - ? const TargetPlatform(.android, .arm64) - : platform; - return _getAndroidArtifactPath(artifact, androidPlatform, mode!); - case .ios: + platform = _mapTargetPlatform(platform); + switch (platform) { + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + assert(platform != TargetPlatform.android); + return _getAndroidArtifactPath(artifact, platform!, mode!); + case TargetPlatform.ios: return _getIosArtifactPath(artifact, platform!, mode, environmentType); - case .macos: - case .linux: - case .windows: + case TargetPlatform.darwin: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: return _getDesktopArtifactPath(artifact, platform!, mode); - case .fuchsia: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: return _getFuchsiaArtifactPath(artifact, platform!, mode!); - case .tester: - case .web: - case .custom: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: case null: return _getHostArtifactPath( artifact, platform ?? _currentHostPlatform(_platform, _operatingSystemUtils), mode, ); - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -657,11 +677,7 @@ class CachedArtifacts implements Artifacts { case Artifact.genSnapshotX64: // For script snapshots any gen_snapshot binary will do. Returning gen_snapshot for // android_arm in profile mode because it is available on all supported host platforms. - return _getAndroidArtifactPath( - artifact, - const TargetPlatform(.android, .armv7), - BuildMode.profile, - ); + return _getAndroidArtifactPath(artifact, TargetPlatform.android_arm, BuildMode.profile); case Artifact.frontendServerSnapshotForEngineDartSdk: return _fileSystem.path.join( _dartSdkPath(_cache), @@ -769,10 +785,13 @@ class CachedArtifacts implements Artifacts { String? _getEngineArtifactsPath(TargetPlatform platform, [BuildMode? mode]) { final String engineDir = _cache.getArtifactDirectory('engine').path; final String platformName = _enginePlatformDirectoryName(platform); - switch (platform.type) { - case .linux: - case .macos: - case .windows: + switch (platform) { + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.darwin: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: // TODO(zanderso): remove once debug desktop artifacts are uploaded // under a separate directory from the host artifacts. // https://github.com/flutter/flutter/issues/38935 @@ -781,25 +800,23 @@ class CachedArtifacts implements Artifacts { } final suffix = mode != BuildMode.debug ? '-${kebabCase(mode.cliName)}' : ''; return _fileSystem.path.join(engineDir, platformName + suffix); - case .fuchsia: - case .tester: - case .web: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: assert(mode == null, 'Platform $platform does not support different build modes.'); return _fileSystem.path.join(engineDir, platformName); - case .ios: - assert(mode != null, 'Need to specify a build mode for platform $platform.'); - final suffix = mode != BuildMode.debug ? '-${kebabCase(mode!.cliName)}' : ''; - return _fileSystem.path.join(engineDir, platformName + suffix); - case .android: - assert( - platform.cpuArch != .unknown, - 'cannot use a generic Android platform to look up artifacts', - ); + case TargetPlatform.ios: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: assert(mode != null, 'Need to specify a build mode for platform $platform.'); final suffix = mode != BuildMode.debug ? '-${kebabCase(mode!.cliName)}' : ''; return _fileSystem.path.join(engineDir, platformName + suffix); - case .custom: - case .unsupported: + case TargetPlatform.android: + assert(false, 'cannot use TargetPlatform.android to look up artifacts'); + return null; + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -809,15 +826,20 @@ class CachedArtifacts implements Artifacts { } TargetPlatform _currentHostPlatform(Platform platform, OperatingSystemUtils operatingSystemUtils) { - final cpuArch = CpuArch.fromHostPlatform(operatingSystemUtils.hostPlatform); if (platform.isMacOS) { - return TargetPlatform(.macos, cpuArch); + return TargetPlatform.darwin; } if (platform.isLinux) { - return TargetPlatform(.linux, cpuArch); + return switch (operatingSystemUtils.hostPlatform) { + HostPlatform.linux_x64 => TargetPlatform.linux_x64, + HostPlatform.linux_riscv64 => TargetPlatform.linux_riscv64, + _ => TargetPlatform.linux_arm64, + }; } if (platform.isWindows) { - return TargetPlatform(.windows, cpuArch); + return operatingSystemUtils.hostPlatform == HostPlatform.windows_arm64 + ? TargetPlatform.windows_arm64 + : TargetPlatform.windows_x64; } throw UnimplementedError('Host OS not supported.'); } @@ -1039,6 +1061,7 @@ class CachedLocalEngineArtifacts implements Artifacts { EnvironmentType? environmentType, }) { platform ??= _currentHostPlatform(_platform, _operatingSystemUtils); + platform = _mapTargetPlatform(platform); final isDirectoryArtifact = artifact == Artifact.flutterPatchedSdkPath; final String? artifactFileName = isDirectoryArtifact ? null @@ -1050,7 +1073,7 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.genSnapshotX64: return _genSnapshotPath(artifact); case Artifact.flutterTester: - return _flutterTesterPath(platform); + return _flutterTesterPath(platform!); case Artifact.isolateSnapshotData: case Artifact.vmSnapshotData: return _fileSystem.path.join( @@ -1066,7 +1089,7 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.flutterMacOSXcframework: return _fileSystem.path.join(localEngineInfo.targetOutPath, artifactFileName); case Artifact.platformKernelDill: - if (platform.type == .fuchsia) { + if (platform == TargetPlatform.fuchsia_x64 || platform == TargetPlatform.fuchsia_arm64) { return _fileSystem.path.join( localEngineInfo.targetOutPath, 'flutter_runner_patched_sdk', @@ -1099,7 +1122,7 @@ class CachedLocalEngineArtifacts implements Artifacts { // what was specified in [mode] argument because local engine will // have only one flutter_patched_sdk in standard location, that // is happen to be what debug(non-release) mode is using. - if (platform.type == .fuchsia) { + if (platform == TargetPlatform.fuchsia_x64 || platform == TargetPlatform.fuchsia_arm64) { return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter_runner_patched_sdk'); } return _getFlutterPatchedSdkPath(BuildMode.debug); @@ -1237,7 +1260,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts { BuildMode? mode, EnvironmentType? environmentType, }) { - if (platform?.type == .web) { + if (platform == TargetPlatform.web_javascript) { switch (artifact) { case Artifact.engineDartSdkPath: return _getDartSdkPath(); @@ -1444,7 +1467,7 @@ class _TestArtifacts implements Artifacts { final buffer = StringBuffer(); buffer.write(artifact); if (platform != null) { - buffer.write('.${_enginePlatformDirectoryName(platform)}'); + buffer.write('.$platform'); } if (mode != null) { buffer.write('.$mode'); @@ -1553,25 +1576,30 @@ String _getFlutterPrebuiltsPath(String baseOutPath, FileSystem fileSystem) { String _getPrebuiltTarget(Platform platform, OperatingSystemUtils operatingSystemUtils) { final TargetPlatform hostPlatform = _currentHostPlatform(platform, operatingSystemUtils); - switch (hostPlatform.type) { - case .macos: + switch (hostPlatform) { + case TargetPlatform.darwin: return 'macos-x64'; - case .linux: - return switch (hostPlatform.cpuArch) { - .riscv64 => 'linux-riscv64', - .arm64 => 'linux-arm64', - _ => 'linux-x64', - }; - case .windows: - return hostPlatform.cpuArch == .arm64 ? 'windows-arm64' : 'windows-x64'; - case .ios: - case .android: - case .fuchsia: - case .web: - case .tester: - case .custom: + case TargetPlatform.linux_riscv64: + return 'linux-riscv64'; + case TargetPlatform.linux_arm64: + return 'linux-arm64'; + case TargetPlatform.linux_x64: + return 'linux-x64'; + case TargetPlatform.windows_x64: + return 'windows-x64'; + case TargetPlatform.windows_arm64: + return 'windows-arm64'; + case TargetPlatform.ios: + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.web_javascript: + case TargetPlatform.tester: throwToolExit('Unsupported host platform: $hostPlatform'); - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index b106b3ca277f2..70af6b2afc9f1 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -356,7 +356,7 @@ class ManifestAssetBundle implements AssetBundle { transformers: const [], ); // Create .bin.json on web builds. - if (targetPlatform.type == .web) { + if (targetPlatform == TargetPlatform.web_javascript) { entries[_kAssetManifestBinJsonFilename] = AssetBundleEntry( DevFSStringContent('""'), kind: AssetKind.regular, @@ -660,7 +660,7 @@ class ManifestAssetBundle implements AssetBundle { _setIfChanged(_kAssetManifestBinFilename, assetManifestBinary, AssetKind.regular); // Create .bin.json on web builds. - if (targetPlatform.type == .web) { + if (targetPlatform == TargetPlatform.web_javascript) { final assetManifestBinaryJson = DevFSStringContent( json.encode(base64.encode(assetManifestBinary.bytes)), ); @@ -719,7 +719,7 @@ class ManifestAssetBundle implements AssetBundle { // On the web, don't compress the NOTICES file since the client doesn't have // dart:io to decompress it. So use the standard _setIfChanged to check if // the strings still match. - if (targetPlatform.type == .web) { + if (targetPlatform == TargetPlatform.web_javascript) { _setIfChanged(_kNoticeFile, DevFSStringContent(combinedLicenses), AssetKind.regular); return; } @@ -1398,11 +1398,7 @@ class ManifestAssetBundle implements AssetBundle { required Set platforms, required List transformers, }) { - _ensureAssetPathIsValid( - assetsBaseDir: assetsBaseDir, - assetUri: assetUri, - packageName: packageName, - ); + _ensureAssetPathIsValid(assetsBaseDir: assetsBaseDir, assetUri: assetUri, packageName: packageName); if (assetUri.pathSegments.first == 'packages' && !_fileSystem.isFileSync( _fileSystem.path.join(assetsBaseDir, _fileSystem.path.fromUri(assetUri)), @@ -1546,7 +1542,7 @@ class _Asset { } bool matchesPlatform(TargetPlatform targetPlatform) { - if (platforms.isEmpty || targetPlatform.type == .tester) { + if (platforms.isEmpty || targetPlatform == TargetPlatform.tester) { return true; } diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 55abebb0bc8fc..d836785258489 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -57,18 +57,26 @@ class GenSnapshot { Future run({ required SnapshotType snapshotType, + // TODO(chingjun): The [CpuArch] parameter is only used for iOS builds (to + // select the correct per-architecture gen_snapshot). This architecture + // information should instead be consolidated into [TargetPlatform] so that + // callers do not need to pass it separately. + CpuArch? cpuArch, Iterable additionalArgs = const [], }) { + assert(cpuArch != CpuArch.armv7); + assert(snapshotType.platform != TargetPlatform.ios || cpuArch != null); final args = [...additionalArgs]; // iOS and macOS have separate gen_snapshot binaries for each target - // architecture (iOS: arm64; macOS: x86_64, arm64). Select the right + // architecture (iOS: armv7, arm64; macOS: x86_64, arm64). Select the right // one for the target architecture in question. Artifact genSnapshotArtifact; - if (snapshotType.platform.type == .ios || snapshotType.platform.type == .macos) { - final CpuArch cpuArch = snapshotType.platform.cpuArch; - assert(cpuArch == .arm64 || cpuArch == .x64); - genSnapshotArtifact = cpuArch == .arm64 ? Artifact.genSnapshotArm64 : Artifact.genSnapshotX64; + if (snapshotType.platform == TargetPlatform.ios || + snapshotType.platform == TargetPlatform.darwin) { + genSnapshotArtifact = cpuArch == CpuArch.arm64 + ? Artifact.genSnapshotArm64 + : Artifact.genSnapshotX64; } else { genSnapshotArtifact = Artifact.genSnapshot; } @@ -109,12 +117,15 @@ class AOTSnapshotter { required BuildMode buildMode, required String mainPath, required String outputPath, + CpuArch? cpuArch, String? sdkRoot, List extraGenSnapshotOptions = const [], String? splitDebugInfo, required bool dartObfuscation, bool quiet = false, }) async { + assert(platform != TargetPlatform.ios || cpuArch != null); + if (!_isValidAotPlatform(platform, buildMode)) { _logger.printError('${platform.getName()} does not support AOT compilation.'); return 1; @@ -125,14 +136,19 @@ class AOTSnapshotter { final genSnapshotArgs = ['--deterministic']; - final bool targetingApplePlatform = platform.type == .ios || platform.type == .macos; + final bool targetingApplePlatform = + platform == TargetPlatform.ios || platform == TargetPlatform.darwin; _logger.printTrace('targetingApplePlatform = $targetingApplePlatform'); final bool extractAppleDebugSymbols = buildMode == BuildMode.profile || buildMode == BuildMode.release; _logger.printTrace('extractAppleDebugSymbols = $extractAppleDebugSymbols'); - final targetingAndroidPlatform = platform.type == .android; + final bool targetingAndroidPlatform = + platform == TargetPlatform.android || + platform == TargetPlatform.android_arm || + platform == TargetPlatform.android_arm64 || + platform == TargetPlatform.android_x64; _logger.printTrace('targetingAndroidPlatform = $targetingAndroidPlatform'); // We strip snapshot by default, but allow to suppress this behavior @@ -156,7 +172,7 @@ class AOTSnapshotter { // library that the end-developer can link into their app. const frameworkName = 'App.framework'; if (!quiet) { - final String targetArch = platform.cpuArch.darwinArchName; + final String targetArch = cpuArch!.darwinArchName; _logger.printStatus('Building $frameworkName for $targetArch...'); } frameworkPath = _fileSystem.path.join(outputPath, frameworkName); @@ -168,7 +184,7 @@ class AOTSnapshotter { // When the minimum version is updated, remember to update // template MinimumOSVersion. // https://github.com/flutter/flutter/pull/62902 - final minOSVersion = platform.type == .ios + final minOSVersion = platform == TargetPlatform.ios ? FlutterDarwinPlatform.ios.deploymentTarget().toString() : FlutterDarwinPlatform.macos.deploymentTarget().toString(); genSnapshotArgs.addAll([ @@ -202,7 +218,7 @@ class AOTSnapshotter { } } - if (platform == const TargetPlatform(.android, .armv7)) { + if (platform == TargetPlatform.android_arm) { // Use softfp for Android armv7 devices. // TODO(cbracken): eliminate this when we fix https://github.com/flutter/flutter/issues/17489 genSnapshotArgs.add('--no-sim-use-hardfp'); @@ -213,9 +229,8 @@ class AOTSnapshotter { // The name of the debug file must contain additional information about // the architecture, since a single build command may produce - // multiple debug files. [TargetPlatform.getName] already includes the - // architecture for the platforms that need it (e.g. `ios-arm64`). - final String archName = platform.getName(); + // multiple debug files. + final String archName = platform.getName(cpuArch: cpuArch); final debugFilename = 'app.$archName.symbols'; final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; if (shouldSplitDebugInfo) { @@ -238,6 +253,7 @@ class AOTSnapshotter { final int genSnapshotExitCode = await _genSnapshot.run( snapshotType: snapshotType, additionalArgs: genSnapshotArgs, + cpuArch: cpuArch, ); if (genSnapshotExitCode != 0) { _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); @@ -285,9 +301,17 @@ class AOTSnapshotter { if (buildMode == BuildMode.debug) { return false; } - return switch (platform.type) { - .android || .ios || .macos || .linux || .windows => true, - _ => false, - }; + return const [ + TargetPlatform.android_arm, + TargetPlatform.android_arm64, + TargetPlatform.android_x64, + TargetPlatform.ios, + TargetPlatform.darwin, + TargetPlatform.linux_x64, + TargetPlatform.linux_arm64, + TargetPlatform.linux_riscv64, + TargetPlatform.windows_x64, + TargetPlatform.windows_arm64, + ].contains(platform); } } diff --git a/packages/flutter_tools/lib/src/base/dds.dart b/packages/flutter_tools/lib/src/base/dds.dart index a8889ee9864e0..e498bdef6028f 100644 --- a/packages/flutter_tools/lib/src/base/dds.dart +++ b/packages/flutter_tools/lib/src/base/dds.dart @@ -10,6 +10,7 @@ import 'package:dds/dds_launcher.dart'; import 'package:meta/meta.dart'; import '../artifacts.dart'; +import '../build_info.dart'; import '../device.dart'; import '../globals.dart' as globals; import '../resident_runner.dart'; @@ -266,7 +267,7 @@ mixin DartDevelopmentServiceLocalOperationsMixin { if (!(await _waitForExtensionsForDevice(device, method))) { return; } - if (device.targetPlatform.type == .web) { + if (device.targetPlatform == TargetPlatform.web_javascript) { await device.vmService!.callMethodWrapper(method, args: params); return; } diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index ed83b8be191a7..551da4d7bc108 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -513,14 +513,14 @@ enum BuildMode { enum EnvironmentType { physical, simulator } String? validatedBuildNumberForPlatform( - PlatformType platformType, + TargetPlatform targetPlatform, String? buildNumber, Logger logger, ) { if (buildNumber == null) { return null; } - if (platformType == .ios || platformType == .macos) { + if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final disallowed = RegExp(r'[^\d\.]'); String tmpBuildNumber = buildNumber.replaceAll(disallowed, ''); @@ -543,7 +543,9 @@ String? validatedBuildNumberForPlatform( } return tmpBuildNumber; } - if (platformType == .android) { + if (targetPlatform == TargetPlatform.android_arm || + targetPlatform == TargetPlatform.android_arm64 || + targetPlatform == TargetPlatform.android_x64) { // See versionCode at https://developer.android.com/studio/publish/versioning final disallowed = RegExp(r'[^\d]'); String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, ''); @@ -563,11 +565,15 @@ String? validatedBuildNumberForPlatform( return buildNumber; } -String? validatedBuildNameForPlatform(PlatformType platformType, String? buildName, Logger logger) { +String? validatedBuildNameForPlatform( + TargetPlatform targetPlatform, + String? buildName, + Logger logger, +) { if (buildName == null) { return null; } - if (platformType == .ios || platformType == .macos) { + if (targetPlatform == TargetPlatform.ios || targetPlatform == TargetPlatform.darwin) { // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html final disallowed = RegExp(r'[^\d\.]'); String tmpBuildName = buildName.replaceAll(disallowed, ''); @@ -590,7 +596,13 @@ String? validatedBuildNameForPlatform(PlatformType platformType, String? buildNa } return tmpBuildName; } - // See versionName at https://developer.android.com/studio/publish/versioning for Android. + if (targetPlatform == TargetPlatform.android || + targetPlatform == TargetPlatform.android_arm || + targetPlatform == TargetPlatform.android_arm64 || + targetPlatform == TargetPlatform.android_x64) { + // See versionName at https://developer.android.com/studio/publish/versioning + return buildName; + } return buildName; } @@ -683,162 +695,109 @@ enum CpuArch { }; } -/// The type of platform (OS / runtime family) that a target or device -/// represents. -/// -/// This is combined with a [CpuArch] to form a [TargetPlatform]. -enum PlatformType { - web, - android, - ios, - linux, - macos, - windows, - fuchsia, - custom, - tester, - unsupported; - - @override - String toString() => name; - - static PlatformType? fromString(String platformType) => values.asNameMap()[platformType]; -} - -/// The platform a Flutter application is built for. -/// -/// A [TargetPlatform] is the combination of a [PlatformType] (the OS / runtime -/// family, e.g. Android or macOS) and a [CpuArch] (the CPU architecture, e.g. -/// arm64). -/// -/// Code that needs to branch on the platform should generally inspect [type] -/// and/or [cpuArch]. -@immutable -final class TargetPlatform { - const TargetPlatform(this.type, this.cpuArch); +enum TargetPlatform { + android('android'), + ios('ios'), + darwin('darwin'), + linux_x64('linux-x64'), + linux_arm64('linux-arm64'), + linux_riscv64('linux-riscv64'), + windows_x64('windows-x64'), + windows_arm64('windows-arm64'), + fuchsia_arm64('fuchsia-arm64'), + fuchsia_x64('fuchsia-x64'), + tester('flutter-tester'), + web_javascript('web-javascript'), + // The arch specific android target platforms are soft-deprecated. + // Instead of using TargetPlatform as a combination arch + platform + // the code will be updated to carry arch information in [CpuArch]. + android_arm('android-arm'), + android_arm64('android-arm64'), + android_x64('android-x64'), + unsupported('unsupported'); + + const TargetPlatform(this._defaultName); factory TargetPlatform.fromName(String name) { return switch (name) { - 'android' => const TargetPlatform(.android, .unknown), - 'android-arm' => const TargetPlatform(.android, .armv7), - 'android-arm64' => const TargetPlatform(.android, .arm64), - 'android-x64' => const TargetPlatform(.android, .x64), - 'fuchsia-arm64' => const TargetPlatform(.fuchsia, .arm64), - 'fuchsia-x64' => const TargetPlatform(.fuchsia, .x64), - // `ios` is architecture-agnostic and defaults to arm64; `ios-arm64` and - // `ios-x64` (simulator) name a specific architecture. - 'ios' || 'ios-arm64' => const TargetPlatform(.ios, .arm64), - 'ios-x64' => const TargetPlatform(.ios, .x64), - 'ios-armv7' => const TargetPlatform(.ios, .armv7), - // `darwin` is architecture-agnostic and defaults to arm64 (Apple - // Silicon); `darwin-x64` and `darwin-arm64` name a specific architecture. - 'darwin' || 'darwin-arm64' => const TargetPlatform(.macos, .arm64), - 'darwin-x64' => const TargetPlatform(.macos, .x64), - 'linux-x64' => const TargetPlatform(.linux, .x64), - 'linux-arm64' => const TargetPlatform(.linux, .arm64), - 'linux-riscv64' => const TargetPlatform(.linux, .riscv64), - 'windows-x64' => const TargetPlatform(.windows, .x64), - 'windows-arm64' => const TargetPlatform(.windows, .arm64), - 'web-javascript' => const TargetPlatform(.web, .unknown), - 'flutter-tester' => const TargetPlatform(.tester, .unknown), + 'android' => TargetPlatform.android, + 'android-arm' => TargetPlatform.android_arm, + 'android-arm64' => TargetPlatform.android_arm64, + 'android-x64' => TargetPlatform.android_x64, + 'fuchsia-arm64' => TargetPlatform.fuchsia_arm64, + 'fuchsia-x64' => TargetPlatform.fuchsia_x64, + 'ios' => TargetPlatform.ios, + // For backward-compatibility and also for Tester, where it must match + // host platform name (HostPlatform.darwin_x64) + 'darwin' || 'darwin-x64' || 'darwin-arm64' => TargetPlatform.darwin, + 'linux-x64' => TargetPlatform.linux_x64, + 'linux-arm64' => TargetPlatform.linux_arm64, + 'linux-riscv64' => TargetPlatform.linux_riscv64, + 'windows-x64' => TargetPlatform.windows_x64, + 'windows-arm64' => TargetPlatform.windows_arm64, + 'web-javascript' => TargetPlatform.web_javascript, + 'flutter-tester' => TargetPlatform.tester, _ => throw Exception('Unsupported platform name "$name"'), }; } - /// The platform type (OS / runtime family). - final PlatformType type; - - /// The CPU architecture of the target. - /// - /// This is [CpuArch.unknown] only when a CPU architecture is not applicable - /// (e.g. web) or has not yet been resolved (e.g. a generic Android target). - final CpuArch cpuArch; - - /// The canonical set of known target platforms. - /// - /// This mirrors the values that used to exist when [TargetPlatform] was an - /// enum, and is primarily useful for tests that need to iterate over all - /// known platforms. - static const List values = [ - TargetPlatform(.android, .unknown), - TargetPlatform(.ios, .arm64), - TargetPlatform(.macos, .arm64), - TargetPlatform(.linux, .x64), - TargetPlatform(.linux, .arm64), - TargetPlatform(.linux, .riscv64), - TargetPlatform(.windows, .x64), - TargetPlatform(.windows, .arm64), - TargetPlatform(.fuchsia, .arm64), - TargetPlatform(.fuchsia, .x64), - TargetPlatform(.tester, .unknown), - TargetPlatform(.web, .unknown), - TargetPlatform(.android, .armv7), - TargetPlatform(.android, .arm64), - TargetPlatform(.android, .x64), - TargetPlatform(.unsupported, .unknown), - ]; - - @override - bool operator ==(Object other) => - other is TargetPlatform && other.type == type && other.cpuArch == cpuArch; - - @override - int get hashCode => Object.hash(type, cpuArch); + final String _defaultName; - /// The canonical string name for this target platform. - /// - /// The name generally follows the `-` convention (e.g. - /// `linux-x64`, `android-arm64`, `ios-arm64`, `darwin-x64`), with a few - /// historical exceptions retained for backward compatibility (e.g. the macOS - /// platform is named `darwin`, and `flutter-tester`). When the architecture - /// is not known ([CpuArch.unknown]), the bare platform name is used (e.g. - /// `ios`, `darwin`, `android`). - String getName() { - return switch (type) { - .android => cpuArch == .unknown ? 'android' : cpuArch.androidPlatformName, - .ios => cpuArch == .unknown ? 'ios' : 'ios-${cpuArch.dartName}', - .macos => cpuArch == .unknown ? 'darwin' : 'darwin-${cpuArch.dartName}', - .linux || .windows || .fuchsia => '${type.name}-${cpuArch.dartName}', - .tester => 'flutter-tester', - .web => 'web-javascript', - .unsupported => 'unsupported', - .custom => throw UnsupportedError('Unexpected target platform $this'), + String getName({CpuArch? cpuArch}) { + return switch (this) { + TargetPlatform.ios when cpuArch != null => 'ios-${cpuArch.darwinArchName}', + TargetPlatform.darwin when cpuArch != null => 'darwin-${cpuArch.darwinArchName}', + _ => _defaultName, }; } - /// The platform name used to identify a device, e.g. in the `flutter devices` - /// output, the daemon protocol, and analytics. - /// - /// This is like [getName], but omits the CPU architecture for iOS and macOS, - /// preserving the historical device platform identifiers (`ios`, `darwin`). - /// The architecture of a device is reported separately (e.g. via the device's - /// `cpuArch`), so it is not duplicated here. - String get devicePlatformName => switch (type) { - .ios => 'ios', - .macos => 'darwin', - _ => getName(), + String get fuchsiaArchForTargetPlatform => switch (this) { + fuchsia_arm64 => 'arm64', + fuchsia_x64 => 'x64', + android || + android_arm || + android_arm64 || + android_x64 || + darwin || + ios || + linux_arm64 || + linux_riscv64 || + linux_x64 || + tester || + web_javascript || + windows_x64 || + windows_arm64 || + unsupported => throw UnsupportedError('Unexpected Fuchsia platform $this'), }; - String get fuchsiaArchForTargetPlatform => switch (type) { - .fuchsia => cpuArch == .arm64 ? 'arm64' : 'x64', - _ => throw UnsupportedError('Unexpected Fuchsia platform $this'), + String get osName => switch (this) { + linux_x64 || linux_arm64 || linux_riscv64 => 'linux', + darwin => 'macos', + windows_x64 || windows_arm64 => 'windows', + android || android_arm || android_arm64 || android_x64 => 'android', + fuchsia_arm64 || fuchsia_x64 => 'fuchsia', + ios => 'ios', + tester => 'flutter-tester', + web_javascript => 'web', + unsupported => throw UnsupportedError('Unexpected target platform $this'), }; - String get osName => switch (type) { - .linux => 'linux', - .macos => 'macos', - .windows => 'windows', - .android => 'android', - .fuchsia => 'fuchsia', - .ios => 'ios', - .tester => 'flutter-tester', - .web => 'web', - .custom || .unsupported => throw UnsupportedError('Unexpected target platform $this'), + String get simpleName => switch (this) { + linux_x64 || darwin || windows_x64 => 'x64', + linux_arm64 || windows_arm64 => 'arm64', + linux_riscv64 => 'riscv64', + android || + android_arm || + android_arm64 || + android_x64 || + fuchsia_arm64 || + fuchsia_x64 || + ios || + tester || + web_javascript || + unsupported => throw UnsupportedError('Unexpected target platform $this'), }; - @override - String toString() => getName(); - static Never throwUnsupportedTarget() => throw UnsupportedError('Target platform is unsupported.'); } @@ -992,7 +951,7 @@ String getWebBuildDirectory() { String getLinuxBuildDirectory([TargetPlatform? targetPlatform, String? flavor]) { final String arch = (targetPlatform == null) ? _getCurrentHostPlatformArchName() - : targetPlatform.cpuArch.dartName; + : targetPlatform.simpleName; final String subDirs = (flavor != null && flavor.isNotEmpty) ? globals.fs.path.join('linux', arch, flavor) : globals.fs.path.join('linux', arch); @@ -1004,7 +963,7 @@ String getLinuxBuildDirectory([TargetPlatform? targetPlatform, String? flavor]) /// When [flavor] is non-empty, a `/` segment is inserted so that /// different flavors can coexist on disk without overwriting each other. String getWindowsBuildDirectory(TargetPlatform targetPlatform, [String? flavor]) { - final String arch = targetPlatform.cpuArch.dartName; + final String arch = targetPlatform.simpleName; final String subDirs = (flavor != null && flavor.isNotEmpty) ? globals.fs.path.join('windows', arch, flavor) : globals.fs.path.join('windows', arch); @@ -1263,8 +1222,8 @@ String? _uncapitalize(String? s) { // flutter_ignore: deprecation_syntax (see analyze.dart) @Deprecated('Use TargetPlatform.getName() instead') -String getNameForTargetPlatform(TargetPlatform platform) { - return platform.getName(); +String getNameForTargetPlatform(TargetPlatform platform, {CpuArch? cpuArch}) { + return platform.getName(cpuArch: cpuArch); } // flutter_ignore: deprecation_syntax (see analyze.dart) diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index 8d90f76f23f59..76c2c31fc15dc 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -77,7 +77,7 @@ abstract class AndroidAssetBundle extends Target { environment, outputDirectory, dartHookResult: dartHookResult, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, buildMode: buildMode, flavor: environment.defines[kFlavor], additionalContent: { @@ -141,7 +141,7 @@ class ProfileAndroidApplication extends CopyFlutterAotBundle { @override List get dependencies => const [ - AotElfProfile(TargetPlatform(.android, .armv7)), + AotElfProfile(TargetPlatform.android_arm), AotAndroidAssetBundle(), ]; } @@ -155,7 +155,7 @@ class ReleaseAndroidApplication extends CopyFlutterAotBundle { @override List get dependencies => const [ - AotElfRelease(TargetPlatform(.android, .armv7)), + AotElfRelease(TargetPlatform.android_arm), AotAndroidAssetBundle(), ]; } @@ -287,12 +287,12 @@ class AndroidAot extends AotElfBase { } // AndroidAot instances used by the bundle rules below. -const androidArmProfile = AndroidAot(TargetPlatform(.android, .armv7), BuildMode.profile); -const androidArm64Profile = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.profile); -const androidx64Profile = AndroidAot(TargetPlatform(.android, .x64), BuildMode.profile); -const androidArmRelease = AndroidAot(TargetPlatform(.android, .armv7), BuildMode.release); -const androidArm64Release = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); -const androidx64Release = AndroidAot(TargetPlatform(.android, .x64), BuildMode.release); +const androidArmProfile = AndroidAot(TargetPlatform.android_arm, BuildMode.profile); +const androidArm64Profile = AndroidAot(TargetPlatform.android_arm64, BuildMode.profile); +const androidx64Profile = AndroidAot(TargetPlatform.android_x64, BuildMode.profile); +const androidArmRelease = AndroidAot(TargetPlatform.android_arm, BuildMode.release); +const androidArm64Release = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); +const androidx64Release = AndroidAot(TargetPlatform.android_x64, BuildMode.release); /// A rule paired with [AndroidAot] that copies the produced so file and manifest.json (if present) into the output directory. class AndroidAotBundle extends Target { 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 12f73fa789b9e..dcbe48c2a6015 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -295,7 +295,7 @@ class CopyAssets extends Target { @override Future build( Environment environment, { - TargetPlatform targetPlatform = const TargetPlatform(.android, .unknown), + TargetPlatform targetPlatform = TargetPlatform.android, }) async { final String? buildModeEnvironment = environment.defines[kBuildMode]; if (buildModeEnvironment == null) { 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 948a573c97a59..b66db673bcc2e 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -92,7 +92,7 @@ class CopyFlutterBundle extends Target { environment, environment.outputDir, dartHookResult: dartHookResult, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, buildMode: buildMode, flavor: flavor, additionalContent: { @@ -229,28 +229,50 @@ class KernelSnapshot extends Target { final String? fileSystemScheme = environment.defines[kFileSystemScheme]; TargetModel targetModel = TargetModel.flutter; - if (targetPlatform.type == .fuchsia) { + if (targetPlatform == TargetPlatform.fuchsia_x64 || + targetPlatform == TargetPlatform.fuchsia_arm64) { targetModel = TargetModel.flutterRunner; } // Force linking of the platform for desktop embedder targets since these // do not correctly load the core snapshots in debug mode. // See https://github.com/flutter/flutter/issues/44724 - final bool forceLinkPlatform = switch (targetPlatform.type) { - .macos || .windows => true, - .linux => targetPlatform.cpuArch == .x64, - .unsupported => TargetPlatform.throwUnsupportedTarget(), - .web || .android || .ios || .fuchsia || .custom || .tester => false, - }; + final bool forceLinkPlatform; + switch (targetPlatform) { + case TargetPlatform.darwin: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: + case TargetPlatform.linux_x64: + forceLinkPlatform = true; + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + forceLinkPlatform = false; + case TargetPlatform.unsupported: + TargetPlatform.throwUnsupportedTarget(); + } - final String? targetOS = switch (targetPlatform.type) { - .fuchsia => 'fuchsia', - .android => 'android', - .macos => 'macos', - .ios => 'ios', - .linux => 'linux', - .windows => 'windows', - .tester || .web || .custom => null, - .unsupported => TargetPlatform.throwUnsupportedTarget(), + final String? targetOS = switch (targetPlatform) { + TargetPlatform.fuchsia_arm64 || TargetPlatform.fuchsia_x64 => 'fuchsia', + TargetPlatform.android || + TargetPlatform.android_arm || + TargetPlatform.android_arm64 || + TargetPlatform.android_x64 => 'android', + TargetPlatform.darwin => 'macos', + TargetPlatform.ios => 'ios', + TargetPlatform.linux_arm64 || + TargetPlatform.linux_riscv64 || + TargetPlatform.linux_x64 => 'linux', + TargetPlatform.windows_arm64 || TargetPlatform.windows_x64 => 'windows', + TargetPlatform.tester || TargetPlatform.web_javascript => null, + TargetPlatform.unsupported => TargetPlatform.throwUnsupportedTarget(), }; final PackageConfig packageConfig = await loadPackageConfigWithLogging( diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart index 64064d60bba13..26b717f5cad88 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart_plugin_registrant.dart @@ -56,7 +56,9 @@ class DartPluginRegistrantTarget extends Target { // code should just do the right thing on every platform. // Failing that, consider throwing if `targetPlatform` isn't set and finding // all violations, as it's not consistently set here. - return targetPlatform?.type == .fuchsia || targetPlatform?.type == .web; + return targetPlatform == TargetPlatform.fuchsia_arm64 || + targetPlatform == TargetPlatform.fuchsia_x64 || + targetPlatform == TargetPlatform.web_javascript; } @override 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 16f707a32aa9e..aeefa12a0c54e 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 @@ -140,7 +140,9 @@ class IconTreeShaker { } // Add space as an optional code point, as web uses it to measure the font height. - final optionalCodePoints = _targetPlatform.type == .web ? [kSpacePoint] : []; + final optionalCodePoints = _targetPlatform == TargetPlatform.web_javascript + ? [kSpacePoint] + : []; result[entry.value] = _IconTreeShakerData( family: entry.key, relativePath: entry.value, 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 4034fe786cdb9..9535489b8f46d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -75,7 +75,7 @@ abstract class AotAssemblyBase extends Target { final List cpuArchs = environment.defines[kIosArchs]?.split(' ').map(getCpuArchForName).toList() ?? [CpuArch.arm64]; - if (targetPlatform.type != .ios) { + if (targetPlatform != TargetPlatform.ios) { throw Exception('aot_assembly is only supported for iOS applications.'); } @@ -108,10 +108,11 @@ abstract class AotAssemblyBase extends Target { } pending.add( snapshotter.build( - platform: TargetPlatform(.ios, cpuArch), + platform: targetPlatform, buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), + cpuArch: cpuArch, sdkRoot: sdkRoot, quiet: true, splitDebugInfo: splitDebugInfo, @@ -162,7 +163,7 @@ class AotAssemblyRelease extends AotAssemblyBase { // it resolves to a file (ios/gen_snapshot) that never exists. This was // split into gen_snapshot_arm64 and gen_snapshot_armv7. // Source.artifact(Artifact.genSnapshot, - // platform: TargetPlatform(.ios, .arm64), + // platform: TargetPlatform.ios, // mode: BuildMode.release, // ), ]; @@ -191,7 +192,7 @@ class AotAssemblyProfile extends AotAssemblyBase { // it resolves to a file (ios/gen_snapshot) that never exists. This was // split into gen_snapshot_arm64 and gen_snapshot_armv7. // Source.artifact(Artifact.genSnapshot, - // platform: TargetPlatform(.ios, .arm64), + // platform: TargetPlatform.ios, // mode: BuildMode.profile, // ), ]; @@ -249,11 +250,7 @@ abstract class UnpackIOS extends UnpackDarwin { const Source.pattern( '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart', ), - Source.artifact( - Artifact.flutterXcframework, - platform: darwinPlatform.targetPlatform, - mode: buildMode, - ), + Source.artifact(Artifact.flutterXcframework, platform: TargetPlatform.ios, mode: buildMode), ]; @override @@ -288,7 +285,7 @@ abstract class UnpackIOS extends UnpackDarwin { environment, environmentType: environmentType, framework: Artifact.flutterFramework, - targetPlatform: darwinPlatform.targetPlatform, + targetPlatform: TargetPlatform.ios, buildMode: buildMode, ); await _copyFrameworkDysm(environment, sdkRoot: sdkRoot, environmentType: environmentType); @@ -314,7 +311,7 @@ abstract class UnpackIOS extends UnpackDarwin { final Directory frameworkDsym = environment.fileSystem.directory( environment.artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: darwinPlatform.targetPlatform, + platform: TargetPlatform.ios, mode: buildMode, environmentType: environmentType, ), @@ -718,7 +715,7 @@ abstract class IosAssetBundle extends Target { environment, assetDirectory, dartHookResult: dartHookResult, - targetPlatform: FlutterDarwinPlatform.ios.targetPlatform, + targetPlatform: TargetPlatform.ios, buildMode: buildMode, additionalInputs: [ flutterProject.ios.infoPlist, 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 ff95a1ca31b25..e42533a8ddfba 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart @@ -140,7 +140,7 @@ class ReleaseUnpackMacOS extends UnpackMacOS { final Directory frameworkDsym = environment.fileSystem.directory( environment.artifacts.getArtifactPath( Artifact.flutterMacOSFrameworkDsym, - platform: darwinPlatform.targetPlatform, + platform: TargetPlatform.darwin, mode: buildMode, ), ); @@ -288,7 +288,7 @@ class CompileMacOSFramework extends Target { ); final targetPlatform = TargetPlatform.fromName(targetPlatformEnvironment); final List cpuArchs = getCpuArchsFromEnv(environment.defines); - if (targetPlatform.type != .macos) { + if (targetPlatform != TargetPlatform.darwin) { throw Exception('compile_macos_framework is only supported for darwin TargetPlatform.'); } @@ -321,7 +321,8 @@ class CompileMacOSFramework extends Target { buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill').path, outputPath: environment.fileSystem.path.join(buildOutputPath, cpuArch.darwinArchName), - platform: TargetPlatform(.macos, cpuArch), + platform: TargetPlatform.darwin, + cpuArch: cpuArch, splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, extraGenSnapshotOptions: extraGenSnapshotOptions, @@ -358,16 +359,10 @@ class CompileMacOSFramework extends Target { List get dependencies => const [KernelSnapshot()]; @override - List get inputs => [ - const Source.pattern('{BUILD_DIR}/app.dill'), - const Source.pattern( - '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart', - ), - Source.artifact( - Artifact.genSnapshot, - mode: BuildMode.release, - platform: FlutterDarwinPlatform.macos.targetPlatform, - ), + List get inputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/macos.dart'), + Source.artifact(Artifact.genSnapshot, mode: BuildMode.release, platform: TargetPlatform.darwin), ]; @override @@ -456,7 +451,7 @@ abstract class MacOSBundleFlutterAssets extends Target { environment, assetDirectory, dartHookResult: dartHookResult, - targetPlatform: FlutterDarwinPlatform.macos.targetPlatform, + targetPlatform: TargetPlatform.darwin, buildMode: buildMode, flavor: flavor, additionalContent: { @@ -510,12 +505,12 @@ abstract class MacOSBundleFlutterAssets extends Target { try { final String vmSnapshotData = environment.artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); final String isolateSnapshotData = environment.artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); environment.fileSystem @@ -587,14 +582,14 @@ class DebugMacOSBundleFlutterAssets extends MacOSBundleFlutterAssets { List get inputs => [ ...super.inputs, const Source.pattern('{BUILD_DIR}/app.dill'), - Source.artifact( + const Source.artifact( Artifact.isolateSnapshotData, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), - Source.artifact( + const Source.artifact( Artifact.vmSnapshotData, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), ]; 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 3bd75b8451e91..f729baee6c4ae 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 @@ -46,7 +46,7 @@ class BuildHooks extends Target { final FileSystem fileSystem = environment.fileSystem; final TargetPlatform targetPlatform = platform == HookPlatform.web - ? const TargetPlatform(.web, .unknown) + ? TargetPlatform.web_javascript : _getTargetPlatformFromEnvironment(environment, name); final Uri projectUri = environment.projectDir.uri; @@ -193,7 +193,7 @@ class LinkHooks extends Target { final Uri projectUri = environment.projectDir.uri; final FileSystem fileSystem = environment.fileSystem; final TargetPlatform targetPlatform = platform == HookPlatform.web - ? const TargetPlatform(.web, .unknown) + ? TargetPlatform.web_javascript : _getTargetPlatformFromEnvironment(environment, name); final String? buildModeEnvironment = environment.defines[kBuildMode]; 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 22eacaf4d4bdf..57775b9065d9d 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -187,10 +187,7 @@ class Dart2JSTarget extends Dart2WebTarget { .getHostArtifact(HostArtifact.webPlatformKernelFolder) .path; final sharedCommandOptions = [ - artifacts.getArtifactPath( - Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), - ), + artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), 'compile', 'js', '--platform-binaries=$platformBinariesPath', @@ -363,10 +360,7 @@ class Dart2WasmTarget extends Dart2WebTarget { final List dartDefines = computeDartDefines(environment); final compilationArgs = [ - artifacts.getArtifactPath( - Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), - ), + artifacts.getArtifactPath(Artifact.engineDartBinary, platform: TargetPlatform.web_javascript), 'compile', 'wasm', '--packages=${findPackageConfigFileOrDefault(environment.projectDir).path}', @@ -671,7 +665,7 @@ class WebReleaseBundle extends Target { environment, environment.outputDir.childDirectory('assets'), dartHookResult: dartHookResult, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, buildMode: buildMode, ); final Depfile bundledDepfile = _bundleLocalRobotoFallback(environment, depfile); diff --git a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart index c3510e054c6ae..a87a36960bc76 100644 --- a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart +++ b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart @@ -175,10 +175,16 @@ class ShaderCompiler { bool _hasLoggedSecurityBlockError = false; List _shaderTargetsFromTargetPlatform(TargetPlatform targetPlatform) { - switch (targetPlatform.type) { - case .android: - case .linux: - case .windows: + switch (targetPlatform) { + case TargetPlatform.android_x64: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: return [ '--sksl', '--runtime-stage-gles', @@ -186,20 +192,20 @@ class ShaderCompiler { '--runtime-stage-vulkan', ]; - case .ios: + case TargetPlatform.ios: return ['--runtime-stage-metal']; - case .macos: + case TargetPlatform.darwin: return ['--sksl', '--runtime-stage-metal']; - case .fuchsia: - case .tester: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.tester: return ['--sksl', '--runtime-stage-vulkan']; - case .web: + case TargetPlatform.web_javascript: return ['--sksl']; - case .custom: - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -242,7 +248,7 @@ class ShaderCompiler { impellerc.path, ...targets, '--iplr', - if (targetPlatform.type == .web) '--json', + if (targetPlatform == TargetPlatform.web_javascript) '--json', '--sl=$outputPath', '--spirv=$outputPath.spirv', '--input=${input.path}', diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 63e626c7245a2..61203a9f33519 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -31,8 +31,8 @@ var _kDefaultTargets = [ // Shared targets const CopyAssets(), const KernelSnapshot(), - const AotElfProfile(TargetPlatform(.android, .armv7)), - const AotElfRelease(TargetPlatform(.android, .armv7)), + const AotElfProfile(TargetPlatform.android_arm), + const AotElfRelease(TargetPlatform.android_arm), const AotAssemblyProfile(), const AotAssemblyRelease(), // macOS targets @@ -44,15 +44,15 @@ var _kDefaultTargets = [ const ProfileUnpackMacOS(), const ReleaseUnpackMacOS(), // Linux targets - const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)), - const DebugBundleLinuxAssets(TargetPlatform(.linux, .arm64)), - const DebugBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), - const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)), - const ProfileBundleLinuxAssets(TargetPlatform(.linux, .arm64)), - const ProfileBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), - const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)), - const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .arm64)), - const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .riscv64)), + const DebugBundleLinuxAssets(TargetPlatform.linux_x64), + const DebugBundleLinuxAssets(TargetPlatform.linux_arm64), + const DebugBundleLinuxAssets(TargetPlatform.linux_riscv64), + const ProfileBundleLinuxAssets(TargetPlatform.linux_x64), + const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64), + const ProfileBundleLinuxAssets(TargetPlatform.linux_riscv64), + const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64), + const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64), + const ReleaseBundleLinuxAssets(TargetPlatform.linux_riscv64), const ReleaseAndroidApplication(), // This is a one-off rule for bundle and aot compat. const CopyFlutterBundle(), @@ -81,14 +81,14 @@ var _kDefaultTargets = [ const ProfileUnpackIOS(), const ReleaseUnpackIOS(), // Windows targets - const UnpackWindows(TargetPlatform(.windows, .x64)), - const UnpackWindows(TargetPlatform(.windows, .arm64)), - const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)), - const DebugBundleWindowsAssets(TargetPlatform(.windows, .arm64)), - const ProfileBundleWindowsAssets(TargetPlatform(.windows, .x64)), - const ProfileBundleWindowsAssets(TargetPlatform(.windows, .arm64)), - const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .x64)), - const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .arm64)), + const UnpackWindows(TargetPlatform.windows_x64), + const UnpackWindows(TargetPlatform.windows_arm64), + const DebugBundleWindowsAssets(TargetPlatform.windows_x64), + const DebugBundleWindowsAssets(TargetPlatform.windows_arm64), + const ProfileBundleWindowsAssets(TargetPlatform.windows_x64), + const ProfileBundleWindowsAssets(TargetPlatform.windows_arm64), + const ReleaseBundleWindowsAssets(TargetPlatform.windows_x64), + const ReleaseBundleWindowsAssets(TargetPlatform.windows_arm64), ]; /// Assemble provides a low level API to interact with the flutter tool build diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index d4df0769a7951..32ef91fc71a45 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -111,27 +111,33 @@ class BuildBundleCommand extends BuildSubCommand { final String targetPlatform = stringArg('target-platform')!; final platform = TargetPlatform.fromName(targetPlatform); // Check for target platforms that are only allowed via feature flags. - switch (platform.type) { - case .macos: + switch (platform) { + case TargetPlatform.darwin: if (!featureFlags.isMacOSEnabled) { throwToolExit('macOS is not a supported target platform.'); } - case .windows: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: if (!featureFlags.isWindowsEnabled) { throwToolExit('Windows is not a supported target platform.'); } - case .linux: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: if (!featureFlags.isLinuxEnabled) { throwToolExit('Linux is not a supported target platform.'); } - case .android: - case .fuchsia: - case .ios: - case .tester: - case .web: - case .custom: + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: break; - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index c6faa58f1c60e..5131df96364ac 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -936,7 +936,7 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { late final Future buildableIOSApp = () async { final app = await applicationPackages?.getPackageForPlatform( - FlutterDarwinPlatform.ios.targetPlatform, + TargetPlatform.ios, buildInfo: await cachedBuildInfo, ) as BuildableIOSApp?; @@ -978,10 +978,7 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { final BuildableIOSApp app = await buildableIOSApp; final logTarget = environmentType == EnvironmentType.simulator ? 'simulator' : 'device'; - final String typeName = globals.artifacts!.getEngineType( - FlutterDarwinPlatform.ios.targetPlatform, - buildInfo.mode, - ); + final String typeName = globals.artifacts!.getEngineType(TargetPlatform.ios, buildInfo.mode); globals.printStatus(switch (xcodeBuildAction) { XcodeBuildAction.build => 'Building $app for $logTarget ($typeName)...', XcodeBuildAction.archive => 'Archiving $app...', 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 e4dcc6eb8eea4..432e9519afc37 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -744,7 +744,7 @@ end final Status status = globals.logger.startProgress(' ├─Copying Flutter.xcframework...'); final String engineCacheFlutterFrameworkDirectory = globals.artifacts!.getArtifactPath( Artifact.flutterXcframework, - platform: FlutterDarwinPlatform.ios.targetPlatform, + platform: TargetPlatform.ios, mode: buildInfo.mode, ); final String flutterFrameworkFileName = globals.fs.path.basename( @@ -798,7 +798,7 @@ end flutterRootDir: globals.fs.directory(Cache.flutterRoot), defines: { kTargetFile: targetFile, - kTargetPlatform: FlutterDarwinPlatform.ios.targetPlatform.getName(), + kTargetPlatform: TargetPlatform.ios.getName(), kIosArchs: defaultIOSArchsForEnvironment( sdkType, globals.artifacts!, diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart index 2ba936945018b..fc8f602e1744c 100644 --- a/packages/flutter_tools/lib/src/commands/build_linux.dart +++ b/packages/flutter_tools/lib/src/commands/build_linux.dart @@ -72,7 +72,7 @@ class BuildLinuxCommand extends BuildSubCommand { final BuildInfo buildInfo = await getBuildInfo(); final targetPlatform = TargetPlatform.fromName(stringArg('target-platform')!); final needCrossBuild = - _operatingSystemUtils.hostPlatform.platformName != targetPlatform.cpuArch.dartName; + _operatingSystemUtils.hostPlatform.platformName != targetPlatform.simpleName; if (!featureFlags.isLinuxEnabled) { throwToolExit( @@ -88,14 +88,14 @@ class BuildLinuxCommand extends BuildSubCommand { } // TODO(fujino): https://github.com/flutter/flutter/issues/74929 if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_x64 && - targetPlatform == const TargetPlatform(.linux, .arm64)) { + targetPlatform == TargetPlatform.linux_arm64) { throwToolExit( 'Cross-build from Linux x64 host to Linux arm64 target is not currently supported.', ); } // Building for riscv64 (on a non-riscv64 host) is experimental if (_operatingSystemUtils.hostPlatform != HostPlatform.linux_riscv64 && - targetPlatform == const TargetPlatform(.linux, .riscv64) && + targetPlatform == TargetPlatform.linux_riscv64 && !featureFlags.isRiscv64SupportEnabled) { throwToolExit( 'Building for Linux riscv64 is currently an experimental feature. To enable, run "flutter config --enable-riscv64"', 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 d26ada8237620..52cf414c511df 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos_framework.dart @@ -258,7 +258,7 @@ end flutterRootDir: globals.fs.directory(Cache.flutterRoot), defines: { kTargetFile: targetFile, - kTargetPlatform: FlutterDarwinPlatform.macos.targetPlatform.getName(), + kTargetPlatform: TargetPlatform.darwin.getName(), kDarwinArchs: defaultMacOSArchsForEnvironment( globals.artifacts!, ).map((CpuArch e) => e.darwinArchName).join(' '), @@ -316,7 +316,7 @@ end final Status status = globals.logger.startProgress(' ├─Copying FlutterMacOS.xcframework...'); final String engineCacheFlutterFrameworkDirectory = globals.artifacts!.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: buildInfo.mode, ); final String flutterFrameworkFileName = globals.fs.path.basename( diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index f88fbe563cf94..fb29bc4b12102 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -468,11 +468,7 @@ class DaemonDomain extends Domain { void handlePlatformType(PlatformType platform) { final reasons = >[]; switch (platform) { - case .tester: - case .unsupported: - // Not user-facing project platforms. - return; - case .linux: + case PlatformType.linux: if (!featureFlags.isLinuxEnabled) { reasons.add({ 'reasonText': 'the Linux feature is not enabled', @@ -487,7 +483,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .macos: + case PlatformType.macos: if (!featureFlags.isMacOSEnabled) { reasons.add({ 'reasonText': 'the macOS feature is not enabled', @@ -502,7 +498,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .windows: + case PlatformType.windows: if (!featureFlags.isWindowsEnabled) { reasons.add({ 'reasonText': 'the Windows feature is not enabled', @@ -518,7 +514,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .ios: + case PlatformType.ios: if (!featureFlags.isIOSEnabled) { reasons.add({ 'reasonText': 'the iOS feature is not enabled', @@ -533,7 +529,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .android: + case PlatformType.android: if (!featureFlags.isAndroidEnabled) { reasons.add({ 'reasonText': 'the Android feature is not enabled', @@ -549,7 +545,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .web: + case PlatformType.web: if (!featureFlags.isWebEnabled) { reasons.add({ 'reasonText': 'the Web feature is not enabled', @@ -564,7 +560,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .fuchsia: + case PlatformType.fuchsia: if (!featureFlags.isFuchsiaEnabled) { reasons.add({ 'reasonText': 'the Fuchsia feature is not enabled', @@ -580,7 +576,7 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } - case .custom: + case PlatformType.custom: if (!featureFlags.areCustomDevicesEnabled) { reasons.add({ 'reasonText': 'the custom devices feature is not enabled', @@ -701,7 +697,7 @@ class AppDomain extends Domain { ResidentRunner runner; - if ((await device.targetPlatform).type == .web) { + if (await device.targetPlatform == TargetPlatform.web_javascript) { runner = webRunnerFactory!.createWebRunner( flutterDevice, flutterProject: flutterProject, @@ -1420,7 +1416,7 @@ Future> _deviceToMap(Device device) async { return { 'id': device.id, 'name': device.displayName, - 'platform': (await device.targetPlatform).devicePlatformName, + 'platform': (await device.targetPlatform).getName(), 'emulator': await device.isLocalEmulator, 'category': device.category?.toString(), 'platformType': device.platformType?.toString(), diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index d092a635bedc0..ff276124bbcf6 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -556,7 +556,7 @@ class RunCommand extends RunCommandBase { if (featureFlags.isWebEnabled && devices != null && devices!.length == 1 && - (await devices!.single.targetPlatform).type == .web) { + await devices!.single.targetPlatform == TargetPlatform.web_javascript) { final WebDevServerConfig webDevServerConfig = await webDevServerConfigCore(); return webDevServerConfig; } @@ -578,7 +578,7 @@ class RunCommand extends RunCommandBase { if (devices!.length > 1) { return '$command/all'; } - return '$command/${(await devices![0].targetPlatform).devicePlatformName}'; + return '$command/${(await devices![0].targetPlatform).getName()}'; } @override @@ -616,12 +616,12 @@ class RunCommand extends RunCommandBase { } else if (devices!.length == 1) { final Device device = devices![0]; final TargetPlatform platform = await device.targetPlatform; - anyAndroidDevices = platform.type == .android; - anyIOSDevices = platform.type == .ios; + anyAndroidDevices = platform == TargetPlatform.android; + anyIOSDevices = platform == TargetPlatform.ios; if (device is IOSDevice && device.isWirelesslyConnected) { anyWirelessIOSDevices = true; } - deviceType = platform.devicePlatformName; + deviceType = platform.getName(); deviceOsVersion = await device.sdkNameAndVersion; isEmulator = await device.isLocalEmulator; } else { @@ -630,8 +630,8 @@ class RunCommand extends RunCommandBase { isEmulator = false; for (final Device device in devices!) { final TargetPlatform platform = await device.targetPlatform; - anyAndroidDevices = anyAndroidDevices || (platform.type == .android); - anyIOSDevices = anyIOSDevices || (platform.type == .ios); + anyAndroidDevices = anyAndroidDevices || (platform == TargetPlatform.android); + anyIOSDevices = anyIOSDevices || (platform == TargetPlatform.ios); if (device is IOSDevice && device.isWirelesslyConnected) { anyWirelessIOSDevices = true; } @@ -723,7 +723,7 @@ class RunCommand extends RunCommandBase { } if (userIdentifier != null && - devices!.every((Device device) => device.platformType != .android)) { + devices!.every((Device device) => device.platformType != PlatformType.android)) { throwToolExit( '--${FlutterOptions.kDeviceUser} is only supported for Android. At least one Android device is required.', ); @@ -967,10 +967,7 @@ class RunCommand extends RunCommandBase { timingLabelParts: [ if (hotMode) 'hot' else 'cold', getBuildMode().cliName, - if (devices!.length == 1) - (await devices![0].targetPlatform).devicePlatformName - else - 'multiple', + if (devices!.length == 1) (await devices![0].targetPlatform).getName() else 'multiple', if (devices!.length == 1 && await devices![0].isLocalEmulator) 'emulator' else null, ], endTimeOverride: appStartedTime, diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index 76d56f0900b20..78f4cd5a4c3c2 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -640,7 +640,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { 'Ensure that `flutter doctor` shows at least one connected device', ); } - if (integrationTestDevice.platformType == .web) { + if (integrationTestDevice.platformType == PlatformType.web) { // TODO(jiahaog): Support web. https://github.com/flutter/flutter/issues/66264 throwToolExit('Web devices are not supported for integration tests yet.'); } @@ -802,7 +802,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { packageConfigPath: packageConfigPath, flavor: flavor, includeAssetsFromDevDependencies: true, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); if (build != 0) { throwToolExit('Error: Failed to build asset bundle'); @@ -811,7 +811,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { await writeBundle( globals.fs.directory(globals.fs.path.join('build', 'unit_test_assets')), assetBundle.entries, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, impellerStatus: impellerStatus, processManager: globals.processManager, fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 40dbf0762e64e..51d2c2797c7d4 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -65,9 +65,9 @@ class TargetModel { /// Infers the appropriate [TargetModel] from a given [TargetPlatform]. static TargetModel fromTargetPlatform(TargetPlatform? platform) { - return switch (platform?.type) { - .web => TargetModel.dartdevc, - .fuchsia => TargetModel.flutterRunner, + return switch (platform) { + TargetPlatform.web_javascript => TargetModel.dartdevc, + TargetPlatform.fuchsia_arm64 || TargetPlatform.fuchsia_x64 => TargetModel.flutterRunner, _ => TargetModel.flutter, }; } @@ -280,7 +280,7 @@ class KernelCompiler { String? nativeAssets, }) async { final TargetPlatform? platform = targetModel == TargetModel.dartdevc - ? const TargetPlatform(.web, .unknown) + ? TargetPlatform.web_javascript : null; // This is a URI, not a file path, so the forward slash is correct even on Windows. if (!sdkRoot.endsWith('/')) { @@ -545,7 +545,7 @@ class ResidentCompilerFactory { TargetModel targetModel = targetModelOverride ?? .flutter; // Configure the compiler to target the DDC runtime. - if (targetPlatform.type == .web) { + if (targetPlatform case .web_javascript) { sdkRoot = artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path; targetModel = .dartdevc; @@ -578,7 +578,7 @@ class ResidentCompilerFactory { ], ); } else { - if (targetPlatform.type == .fuchsia) { + if (targetPlatform case .fuchsia_arm64 || .fuchsia_x64) { targetModel = .flutterRunner; } buildInfo = buildInfo.copyWith( @@ -935,7 +935,7 @@ class DefaultResidentCompiler implements ResidentCompiler { String? nativeAssetsUri, }) async { final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) - ? const TargetPlatform(.web, .unknown) + ? TargetPlatform.web_javascript : null; late final List commandToStartFrontendServer; if (frontendServerStarterPath != null && frontendServerStarterPath!.isNotEmpty) { diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart index f9c177001ed44..55e70bf42a030 100644 --- a/packages/flutter_tools/lib/src/custom_devices/custom_device.dart +++ b/packages/flutter_tools/lib/src/custom_devices/custom_device.dart @@ -776,15 +776,14 @@ class CustomDevice extends Device { } @override - Future get targetPlatform async => - _config.platform ?? const TargetPlatform(.linux, .arm64); + Future get targetPlatform async => _config.platform ?? TargetPlatform.linux_arm64; @override Future get cpuArch async { // Custom devices only support Linux target platforms (see // CustomDeviceConfig), so the arch is derived from that. - return switch (_config.platform?.cpuArch) { - .x64 => CpuArch.x64, + return switch (_config.platform) { + TargetPlatform.linux_x64 => CpuArch.x64, _ => CpuArch.arm64, }; } diff --git a/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart b/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart index 535c33850be7c..ad2080cf6619a 100644 --- a/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart +++ b/packages/flutter_tools/lib/src/custom_devices/custom_device_config.dart @@ -98,8 +98,8 @@ class CustomDeviceConfig { }) : assert(forwardPortCommand == null || forwardPortSuccessRegex != null), assert( platform == null || - platform == const TargetPlatform(.linux, .x64) || - platform == const TargetPlatform(.linux, .arm64), + platform == TargetPlatform.linux_x64 || + platform == TargetPlatform.linux_arm64, ); /// Create a CustomDeviceConfig from some JSON value. @@ -144,8 +144,8 @@ class CustomDeviceConfig { } if (platform != null && - platform != const TargetPlatform(.linux, .arm64) && - platform != const TargetPlatform(.linux, .x64)) { + platform != TargetPlatform.linux_arm64 && + platform != TargetPlatform.linux_x64) { throw const CustomDeviceRevivalException.fromDescriptions( _kPlatform, 'null or one of linux-arm64, linux-x64', @@ -243,7 +243,7 @@ class CustomDeviceConfig { id: 'pi', label: 'Raspberry Pi', sdkNameAndVersion: 'Raspberry Pi 4 Model B+', - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, enabled: false, pingCommand: const ['ping', '-w', '500', '-n', '1', 'raspberrypi'], pingSuccessRegex: RegExp(r'[<=]\d+ms'), diff --git a/packages/flutter_tools/lib/src/darwin/darwin.dart b/packages/flutter_tools/lib/src/darwin/darwin.dart index 3a1f10980e47b..87feaf935738f 100644 --- a/packages/flutter_tools/lib/src/darwin/darwin.dart +++ b/packages/flutter_tools/lib/src/darwin/darwin.dart @@ -18,7 +18,7 @@ import '../project.dart'; enum FlutterDarwinPlatform { ios( binaryName: 'Flutter', - targetPlatform: TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, swiftPackagePlatform: SwiftPackagePlatform.ios, artifactName: 'ios', artifactZip: 'artifacts.zip', @@ -27,7 +27,7 @@ enum FlutterDarwinPlatform { ), macos( binaryName: 'FlutterMacOS', - targetPlatform: TargetPlatform(.macos, .x64), + targetPlatform: TargetPlatform.darwin, swiftPackagePlatform: SwiftPackagePlatform.macos, artifactName: 'darwin-x64', artifactZip: 'framework.zip', @@ -110,10 +110,7 @@ enum FlutterDarwinPlatform { /// Returns corresponding [FlutterDarwinPlatform] for the [targetPlatform]. static FlutterDarwinPlatform? fromTargetPlatform(TargetPlatform targetPlatform) { for (final FlutterDarwinPlatform darwinPlatform in FlutterDarwinPlatform.values) { - // Match on the platform type (family) only. A [FlutterDarwinPlatform] - // describes an OS (iOS or macOS) and is architecture-agnostic, whereas - // [TargetPlatform] equality also considers the CPU architecture. - if (targetPlatform.type == darwinPlatform.targetPlatform.type) { + if (targetPlatform == darwinPlatform.targetPlatform) { return darwinPlatform; } } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 423e8d601b798..13382fec0471b 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -41,6 +41,23 @@ enum Category { } } +/// The platform sub-folder that a device type supports. +enum PlatformType { + web, + android, + ios, + linux, + macos, + windows, + fuchsia, + custom; + + @override + String toString() => name; + + static PlatformType? fromString(String platformType) => values.asNameMap()[platformType]; +} + /// A discovery mechanism for flutter-supported development devices. abstract class DeviceManager { DeviceManager({required Logger logger}) : _logger = logger; @@ -339,8 +356,9 @@ class DeviceDiscoverySupportFilter { Future isDeviceSupportedForAll(Device device) async { final TargetPlatform devicePlatform = await device.targetPlatform; return await device.isSupported() && - devicePlatform.type != .fuchsia && - devicePlatform.type != .web && + devicePlatform != TargetPlatform.fuchsia_arm64 && + devicePlatform != TargetPlatform.fuchsia_x64 && + devicePlatform != TargetPlatform.web_javascript && await isDeviceSupportedForProject(device); } @@ -687,16 +705,13 @@ abstract class Device { Future supportMessage() async => await isSupported() ? 'Supported' : 'Unsupported'; /// The device's platform. - /// - /// By default this is derived from [platformType] and [cpuArch]. Subclasses - /// may override this if they need custom behavior. - Future get targetPlatform async => TargetPlatform(platformType!, await cpuArch); + Future get targetPlatform; /// The CPU architecture of the device. Future get cpuArch; /// Platform name for display only. - Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; + Future get targetPlatformDisplayName async => (await targetPlatform).getName(); Future get sdkNameAndVersion; @@ -834,7 +849,7 @@ abstract class Device { var supportIndicator = await device.isSupported() ? '' : ' (unsupported)'; final TargetPlatform targetPlatform = await device.targetPlatform; if (await device.isLocalEmulator) { - final type = targetPlatform.type == .ios ? 'simulator' : 'emulator'; + final type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator'; supportIndicator += ' ($type)'; } table.add([ @@ -870,7 +885,7 @@ abstract class Device { 'name': name, 'id': id, 'isSupported': await isSupported(), - 'targetPlatform': (await targetPlatform).devicePlatformName, + 'targetPlatform': (await targetPlatform).getName(), 'cpuArch': (await cpuArch).name, 'emulator': isLocalEmu, 'sdk': await sdkNameAndVersion, diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart index fcfe2d6c2dfe0..1dbd5c9cbab57 100644 --- a/packages/flutter_tools/lib/src/emulator.dart +++ b/packages/flutter_tools/lib/src/emulator.dart @@ -15,7 +15,6 @@ import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/process.dart'; -import 'build_info.dart'; import 'device.dart'; import 'ios/ios_emulators.dart'; diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart index 9ff3c40687b12..c61de499a28c0 100644 --- a/packages/flutter_tools/lib/src/flutter_application_package.dart +++ b/packages/flutter_tools/lib/src/flutter_application_package.dart @@ -49,8 +49,11 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { BuildInfo? buildInfo, File? applicationBinary, }) async { - switch (platform.type) { - case .android: + switch (platform) { + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: if (applicationBinary == null) { return AndroidApk.fromAndroidProject( FlutterProject.current().android, @@ -71,32 +74,35 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { userMessages: _userMessages, processUtils: _processUtils, ); - case .ios: + case TargetPlatform.ios: return applicationBinary == null ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo) : IOSApp.fromPrebuiltApp(applicationBinary); - case .tester: + case TargetPlatform.tester: return FlutterTesterApp.fromCurrentDirectory(globals.fs); - case .macos: + case TargetPlatform.darwin: return applicationBinary == null ? MacOSApp.fromMacOSProject(FlutterProject.current().macos) : MacOSApp.fromPrebuiltApp(applicationBinary); - case .web: + case TargetPlatform.web_javascript: if (!FlutterProject.current().web.existsSync()) { return null; } return WebApplicationPackage(FlutterProject.current()); - case .linux: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: return applicationBinary == null ? LinuxApp.fromLinuxProject(FlutterProject.current().linux) : LinuxApp.fromPrebuiltApp(applicationBinary); - case .windows: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: return applicationBinary == null ? WindowsApp.fromWindowsProject(FlutterProject.current().windows) : WindowsApp.fromPrebuiltApp(applicationBinary); - case .fuchsia: - case .custom: - case .unsupported: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 89d9c2aab5586..48f9fa1ebdb70 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -1237,6 +1237,9 @@ class IOSDevice extends Device { return _coreDeviceLauncher.stopApp(deviceId: id); } + @override + Future get targetPlatform async => TargetPlatform.ios; + @override Future get sdkNameAndVersion async => 'iOS ${_sdkVersion ?? 'unknown version'}'; diff --git a/packages/flutter_tools/lib/src/ios/ios_emulators.dart b/packages/flutter_tools/lib/src/ios/ios_emulators.dart index 2800d04b9b77d..348312240b2fa 100644 --- a/packages/flutter_tools/lib/src/ios/ios_emulators.dart +++ b/packages/flutter_tools/lib/src/ios/ios_emulators.dart @@ -4,7 +4,6 @@ import '../base/common.dart'; import '../base/process.dart'; -import '../build_info.dart'; import '../device.dart'; import '../emulator.dart'; import '../globals.dart' as globals; diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index c9dc71e1986fe..4a4fa844df7f6 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -463,7 +463,7 @@ Future buildXcodeProject({ if (!hasWatchCompanion) { // ONLY_ACTIVE_ARCH specifies whether the product includes only code for // the native architecture. - final onlyActiveArch = activeArch == .fromHostPlatform(getCurrentHostPlatform()); + final onlyActiveArch = activeArch == CpuArch.fromHostPlatform(getCurrentHostPlatform()); buildCommands.add('ONLY_ACTIVE_ARCH=${onlyActiveArch ? 'YES' : 'NO'}'); buildCommands.add('ARCHS=${activeArch.darwinArchName}'); @@ -729,7 +729,7 @@ bool publicHeadersChanged({ }) { final String? basePath = artifacts?.getArtifactPath( Artifact.flutterFramework, - platform: FlutterDarwinPlatform.ios.targetPlatform, + platform: TargetPlatform.ios, mode: mode, environmentType: environmentType, ); diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 645e00c771bfd..f91b650e56adc 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -622,6 +622,9 @@ class IOSSimulator extends Device { ); } + @override + Future get targetPlatform async => TargetPlatform.ios; + @override Future get sdkNameAndVersion async => simulatorCategory; diff --git a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart index 3ac5662b0dbf9..30f8d09d65a53 100644 --- a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart +++ b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart @@ -7,7 +7,6 @@ import '../base/common.dart'; import '../base/file_system.dart'; import '../build_info.dart'; import '../cache.dart'; -import '../darwin/darwin.dart'; import '../flutter_manifest.dart'; import '../globals.dart' as globals; import '../project.dart'; @@ -16,7 +15,7 @@ import 'xcodeproj.dart'; String flutterMacOSFrameworkDir(BuildMode mode, FileSystem fileSystem, Artifacts artifacts) { final String flutterMacOSFramework = artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: FlutterDarwinPlatform.macos.targetPlatform, + platform: TargetPlatform.darwin, mode: mode, ); return fileSystem.path.normalize(fileSystem.path.dirname(flutterMacOSFramework)); @@ -134,14 +133,14 @@ void _updateGeneratedEnvironmentVariablesScript({ /// Build name parsed and validated from build info and manifest. Used for CFBundleShortVersionString. String? parsedBuildName({required FlutterManifest manifest, BuildInfo? buildInfo}) { final String? buildNameToParse = buildInfo?.buildName ?? manifest.buildName; - return validatedBuildNameForPlatform(PlatformType.ios, buildNameToParse, globals.logger); + return validatedBuildNameForPlatform(TargetPlatform.ios, buildNameToParse, globals.logger); } /// Build number parsed and validated from build info and manifest. Used for CFBundleVersion. String? parsedBuildNumber({required FlutterManifest manifest, BuildInfo? buildInfo}) { String? buildNumberToParse = buildInfo?.buildNumber ?? manifest.buildNumber; final String? buildNumber = validatedBuildNumberForPlatform( - PlatformType.ios, + TargetPlatform.ios, buildNumberToParse, globals.logger, ); @@ -151,7 +150,7 @@ String? parsedBuildNumber({required FlutterManifest manifest, BuildInfo? buildIn // Drop back to parsing build name if build number is not present. Build number is optional in the manifest, but // FLUTTER_BUILD_NUMBER is required as the backing value for the required CFBundleVersion. buildNumberToParse = buildInfo?.buildName ?? manifest.buildName; - return validatedBuildNumberForPlatform(PlatformType.ios, buildNumberToParse, globals.logger); + return validatedBuildNumberForPlatform(TargetPlatform.ios, buildNumberToParse, globals.logger); } /// List of lines of build settings. Example: 'FLUTTER_BUILD_DIR=build' diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 581c51f509fc6..bd8702f5905ca 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -480,7 +480,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', @@ -494,7 +494,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', @@ -516,7 +516,7 @@ class WebDevFS implements DevFS { fileSystem.path.join( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index 654d6d6ae60b2..3e0ec38639659 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -97,7 +97,10 @@ Future runFlutterSpecificHooks({ buildDataAssets: buildDataAssets, ); - final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); + final BuildMode buildMode = _getBuildMode( + environmentDefines, + targetPlatform == TargetPlatform.tester, + ); final bool linkingEnabled = _nativeAssetsLinkingEnabled(buildMode); final DartHooksResult linkResult; if (linkingEnabled) { @@ -222,7 +225,10 @@ List _getTargets({ if (featureFlags.isDartDataAssetsEnabled && buildDataAssets) SupportedAssetTypes.dataAssets, ]; - final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); + final BuildMode buildMode = _getBuildMode( + environmentDefines, + targetPlatform == TargetPlatform.tester, + ); return AssetBuildTarget.targetsFor( targetPlatform: targetPlatform, @@ -256,7 +262,10 @@ Future<({List targets, BuildMode buildMode, bool linkingEnable if (featureFlags.isDartDataAssetsEnabled && buildDataAssets) SupportedAssetTypes.dataAssets, ]; - final BuildMode buildMode = _getBuildMode(environmentDefines, targetPlatform.type == .tester); + final BuildMode buildMode = _getBuildMode( + environmentDefines, + targetPlatform == TargetPlatform.tester, + ); if (supportedAssetTypes.contains(SupportedAssetTypes.codeAssets)) { for (final CodeAssetTarget target in targets.whereType()) { @@ -476,7 +485,7 @@ Future> installCodeAssets({ required Uri targetUri, }) async { final OS targetOS = getNativeOSFromTargetPlatform(targetPlatform); - final flutterTester = targetPlatform.type == .tester; + final flutterTester = targetPlatform == TargetPlatform.tester; final BuildMode buildMode = _getBuildMode(environmentDefines, flutterTester); final String? codesignIdentity = environmentDefines[kCodesignIdentity]; @@ -953,20 +962,27 @@ Never _throwNativeAssetsLinkFailed() { } OS getNativeOSFromTargetPlatform(TargetPlatform platform) { - switch (platform.type) { - case .ios: + switch (platform) { + case TargetPlatform.ios: return OS.iOS; - case .macos: + case TargetPlatform.darwin: return OS.macOS; - case .linux: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: return OS.linux; - case .windows: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: return OS.windows; - case .fuchsia: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: return OS.fuchsia; - case .android: + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: return OS.android; - case .tester: + case TargetPlatform.tester: if (const LocalPlatform().isMacOS) { return OS.macOS; } else if (const LocalPlatform().isLinux) { @@ -976,10 +992,9 @@ OS getNativeOSFromTargetPlatform(TargetPlatform platform) { } else { throw StateError('Unknown operating system'); } - case .web: + case TargetPlatform.web_javascript: throw StateError('No dart builds for web yet.'); - case .custom: - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart index 2b3129b424792..afe0ed360744e 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/targets.dart @@ -67,32 +67,33 @@ sealed class AssetBuildTarget { required List supportedAssetTypes, required Directory? buildDirectory, }) { - switch (targetPlatform.type) { - case .windows: - return _windowsTarget( - supportedAssetTypes, - targetPlatform.cpuArch == .arm64 ? Architecture.arm64 : Architecture.x64, - ); - case .linux: - final Architecture architecture = switch (targetPlatform.cpuArch) { - .arm64 => Architecture.arm64, - .riscv64 => Architecture.riscv64, - _ => Architecture.x64, - }; - return _linuxTarget(supportedAssetTypes, architecture, buildMode, buildDirectory); - case .macos: + switch (targetPlatform) { + case TargetPlatform.windows_x64: + return _windowsTarget(supportedAssetTypes, Architecture.x64); + case TargetPlatform.linux_x64: + return _linuxTarget(supportedAssetTypes, Architecture.x64, buildMode, buildDirectory); + case TargetPlatform.linux_arm64: + return _linuxTarget(supportedAssetTypes, Architecture.arm64, buildMode, buildDirectory); + case TargetPlatform.linux_riscv64: + return _linuxTarget(supportedAssetTypes, Architecture.riscv64, buildMode, buildDirectory); + case TargetPlatform.windows_arm64: + return _windowsTarget(supportedAssetTypes, Architecture.arm64); + case TargetPlatform.darwin: return _macTargets(environmentDefines, supportedAssetTypes); - case .android: + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: return _androidTargets(targetPlatform, environmentDefines, supportedAssetTypes); - case .ios: + case TargetPlatform.ios: return _iosTargets(environmentDefines, fileSystem, supportedAssetTypes); - case .web: + case TargetPlatform.web_javascript: return _webTarget(supportedAssetTypes); - case .tester: + case TargetPlatform.tester: return _flutterTesterTarget(supportedAssetTypes); - case .fuchsia: - case .custom: - case .unsupported: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.unsupported: throwToolExit('No targets defined for target platform $targetPlatform.'); } } @@ -418,16 +419,32 @@ final class FlutterTesterAssetTarget extends CodeAssetTarget { } List _androidArchs(TargetPlatform targetPlatform, String? androidArchsEnvironment) { - if (targetPlatform.type != .android) { - throwToolExit('Unsupported Android target platform: $targetPlatform.'); + switch (targetPlatform) { + case TargetPlatform.android_arm: + return [CpuArch.armv7]; + case TargetPlatform.android_arm64: + return [CpuArch.arm64]; + case TargetPlatform.android_x64: + return [CpuArch.x64]; + case TargetPlatform.android: + if (androidArchsEnvironment == null) { + throw MissingDefineException(kAndroidArchs, 'native_assets'); + } + return androidArchsEnvironment.split(' ').map(getCpuArchForName).toList(); + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: + case TargetPlatform.unsupported: + throwToolExit('Unsupported Android target platform: $targetPlatform.'); } - return switch (targetPlatform.cpuArch) { - .armv7 || .arm64 || .x64 => [targetPlatform.cpuArch], - CpuArch.unknown when androidArchsEnvironment != null => - androidArchsEnvironment.split(' ').map(getCpuArchForName).toList(), - .unknown => throw MissingDefineException(kAndroidArchs, 'native_assets'), - .x86 || .riscv64 => throwToolExit('Unsupported Android target platform: $targetPlatform.'), - }; } String? _emptyToNull(String? input) { diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart index 0adf4ba0946da..73bc965e29e4e 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/test/native_assets.dart @@ -62,7 +62,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { // Only `flutter test` uses the // `build/native_assets//native_assets.json` file which uses absolute // paths to the shared libraries. - final OS targetOS = getNativeOSFromTargetPlatform(const TargetPlatform(.tester, .unknown)); + final OS targetOS = getNativeOSFromTargetPlatform(TargetPlatform.tester); final String buildDir = getBuildDirectory(); final String osName = targetOS.name; final Uri buildUri = projectUri.resolve('$buildDir/native_assets/$osName/'); @@ -74,7 +74,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, buildRunner: buildRunner, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, projectUri: projectUri, fileSystem: globals.fs, buildCodeAssets: const BuildCodeAssetsOptions( @@ -89,7 +89,7 @@ Future testCompilerBuildNativeAssets(BuildInfo buildInfo) async { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, projectUri: projectUri, fileSystem: globals.fs, nativeAssetsFileUri: nativeAssetsFileUri, diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index f6e5879385f75..6f49a8bf4470d 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -263,7 +263,7 @@ class ResidentWebRunner extends ResidentRunner { }) async { final ApplicationPackage? package = await ApplicationPackageFactory.instance! .getPackageForPlatform( - const TargetPlatform(.web, .unknown), + TargetPlatform.web_javascript, buildInfo: debuggingOptions.buildInfo, ); if (package == null) { @@ -292,9 +292,7 @@ class ResidentWebRunner extends ResidentRunner { ? WebExpressionCompiler(flutterDevice!.generator!, fileSystem: _fileSystem) : null; - flutterDevice!.developmentShaderCompiler.configureCompiler( - const TargetPlatform(.web, .unknown), - ); + flutterDevice!.developmentShaderCompiler.configureCompiler(TargetPlatform.web_javascript); flutterDevice!.devFS = WebDevFS( webDevServerConfig: updatedConfig, @@ -463,7 +461,7 @@ class ResidentWebRunner extends ResidentRunner { status = _logger.startProgress('Performing hot reload...', progressId: 'hot.reload'); } - final String targetPlatform = const TargetPlatform(.web, .unknown).getName(); + final String targetPlatform = TargetPlatform.web_javascript.getName(); final String sdkName = await flutterDevice!.device!.sdkNameAndVersion; // Will be null if there is no report. @@ -752,12 +750,12 @@ class ResidentWebRunner extends ResidentRunner { _logger.printTrace('Updating assets'); final int result = await assetBundle.build( flutterHookResult: await dartBuilder?.runHooks( - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, environment: environment, logger: _logger, ), packageConfigPath: debuggingOptions.buildInfo.packageConfigPath, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); if (result != 0) { return UpdateFSReport(); diff --git a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart index ed794b514db8e..8ba8c0f06a65a 100644 --- a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart +++ b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart @@ -737,7 +737,7 @@ _flutter.buildConfig = ${jsonEncode(buildConfig)}; .directory( globals.artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), ) .parent; diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index af5c5f23b1bbc..d13755bd1ac1f 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -168,8 +168,10 @@ Future _runCmake( await buildDir.create(recursive: true); final String buildFlag = sentenceCase(buildModeName); - final bool needCrossBuildOptionsForArm64 = needCrossBuild && targetPlatform.cpuArch == .arm64; - final bool needCrossBuildOptionsForRiscv64 = needCrossBuild && targetPlatform.cpuArch == .riscv64; + final bool needCrossBuildOptionsForArm64 = + needCrossBuild && targetPlatform == TargetPlatform.linux_arm64; + final bool needCrossBuildOptionsForRiscv64 = + needCrossBuild && targetPlatform == TargetPlatform.linux_riscv64; int result; if (!globals.processManager.canRun('cmake')) { throwToolExit(globals.userMessages.cmakeMissing); diff --git a/packages/flutter_tools/lib/src/linux/linux_device.dart b/packages/flutter_tools/lib/src/linux/linux_device.dart index 9cec26d20e532..13a3a73873dfd 100644 --- a/packages/flutter_tools/lib/src/linux/linux_device.dart +++ b/packages/flutter_tools/lib/src/linux/linux_device.dart @@ -40,6 +40,16 @@ class LinuxDevice extends DesktopDevice { @override String get name => 'Linux'; + @override + late final Future targetPlatform = () async { + if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_x64) { + return TargetPlatform.linux_x64; + } else if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_riscv64) { + return TargetPlatform.linux_riscv64; + } + return TargetPlatform.linux_arm64; + }(); + @override Future get cpuArch async => CpuArch.fromHostPlatform(_operatingSystemUtils.hostPlatform); diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 8c465ba7afe55..dee64414d7052 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -42,6 +42,9 @@ class MacOSDevice extends DesktopDevice { @override bool get supportsFlavors => true; + @override + Future get targetPlatform async => TargetPlatform.darwin; + @override Future get cpuArch async => CpuArch.fromHostPlatform(_operatingSystemUtils.hostPlatform); diff --git a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart index 93965242ad361..6255712535097 100644 --- a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart @@ -35,6 +35,9 @@ class MacOSDesignedForIPadDevice extends DesktopDevice { @override String get name => 'Mac Designed for iPad'; + @override + Future get targetPlatform async => TargetPlatform.darwin; + // "Designed for iPad" apps are only supported on Apple Silicon Macs. @override Future get cpuArch async => CpuArch.arm64; diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart index 1cd1be7c91814..0840c95b6373a 100644 --- a/packages/flutter_tools/lib/src/mdns_discovery.dart +++ b/packages/flutter_tools/lib/src/mdns_discovery.dart @@ -579,8 +579,8 @@ class MDnsVmServiceDiscovery { return; } final TargetPlatform targetPlatform = await device.targetPlatform; - switch (targetPlatform.type) { - case .ios: + switch (targetPlatform) { + case TargetPlatform.ios: _analytics.send( Event.appleUsageEvent(workflow: 'ios-mdns', parameter: 'no-ipv4-link-local'), ); @@ -591,16 +591,22 @@ class MDnsVmServiceDiscovery { 'under System Preferences > Network > iPhone USB. ' 'See https://github.com/flutter/flutter/issues/46698 for details.', ); - case .android: - case .macos: - case .fuchsia: - case .linux: - case .tester: - case .web: - case .windows: - case .custom: + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: _logger.printTrace('No interface with an ipv4 link local address was found.'); - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } diff --git a/packages/flutter_tools/lib/src/proxied_devices/devices.dart b/packages/flutter_tools/lib/src/proxied_devices/devices.dart index b9beb1dc8f8a0..65fd5e1cc69f7 100644 --- a/packages/flutter_tools/lib/src/proxied_devices/devices.dart +++ b/packages/flutter_tools/lib/src/proxied_devices/devices.dart @@ -493,7 +493,7 @@ class ProxiedDevice extends Device { final String id = _cast( await connection.sendRequest('device.uploadApplicationPackage', { - 'targetPlatform': _targetPlatform.devicePlatformName, + 'targetPlatform': _targetPlatform.getName(), 'applicationBinary': fileName, }), ); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index fa37c80ba4767..12d366f9f066c 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -738,7 +738,7 @@ abstract class ResidentHandlers { return false; } for (final FlutterDevice? device in flutterDevices) { - if (device!.targetPlatform.type == .web) { + if (device!.targetPlatform == TargetPlatform.web_javascript) { continue; } final List views = await device.vmService!.getFlutterViews(); @@ -891,7 +891,7 @@ abstract class ResidentHandlers { } Future _takeVmServiceScreenshot(FlutterDevice device, File outputFile) async { - if (device.targetPlatform.type != .web) { + if (device.targetPlatform != TargetPlatform.web_javascript) { return false; } assert(supportsServiceProtocol); @@ -1487,7 +1487,7 @@ abstract class ResidentRunner extends ResidentHandlers { commandHelp.b.print(); } else { final bool isRunningOnWeb = flutterDevices.every((FlutterDevice? flutterDevice) { - return flutterDevice?.targetPlatform.type == .web; + return flutterDevice?.targetPlatform == TargetPlatform.web_javascript; }); if (!isRunningOnWeb) { @@ -1556,7 +1556,7 @@ abstract class ResidentRunner extends ResidentHandlers { } // 3. Perform the standard, cross-platform eviction calls. - final supportsShaderReload = device.targetPlatform.type != .web; + final supportsShaderReload = device.targetPlatform != TargetPlatform.web_javascript; for (final String assetPath in devFS.assetPathsToEvict) { // Flutter GPU shader bundles reload the compiled ShaderLibrary in place // via the `ext.ui.gpu.reinitializeShaderLibrary` extension. It is @@ -1578,7 +1578,7 @@ abstract class ResidentRunner extends ResidentHandlers { // this throws an internal RPCError (-32603) instead of a standard MethodNotFound // error, which would break the hot reload. // See https://github.com/flutter/flutter/issues/137265 - if (device.targetPlatform.type != .web) { + if (device.targetPlatform != TargetPlatform.web_javascript) { for (final String assetPath in devFS.shaderPathsToEvict) { futures.add(vmService.flutterEvictShader(assetPath, isolateId: firstUiIsolate.id!)); } @@ -1637,25 +1637,28 @@ class OperationResultExtraTiming { } Future getMissingPackageHintForPlatform(TargetPlatform platform) async { - switch (platform.type) { - case .android: - if (platform.cpuArch == .unknown) { - return null; - } + switch (platform) { + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: final FlutterProject project = FlutterProject.current(); final String manifestPath = globals.fs.path.relative(project.android.appManifestFile.path); return 'Is your project missing an $manifestPath?\nConsider running "flutter create ." to create one.'; - case .ios: + case TargetPlatform.ios: return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.'; - case .macos: - case .fuchsia: - case .linux: - case .tester: - case .web: - case .windows: - case .custom: + case TargetPlatform.android: + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: return null; - case .unsupported: + case TargetPlatform.unsupported: TargetPlatform.throwUnsupportedTarget(); } } @@ -1911,12 +1914,14 @@ class TerminalHandler { final bool isRunningOnWeb = residentRunner.flutterDevices.every(( FlutterDevice? flutterDevice, ) { - return flutterDevice?.targetPlatform.type == .web; + return flutterDevice?.targetPlatform == TargetPlatform.web_javascript; }); if (residentRunner.isRunningDebug || !isRunningOnWeb) { // DevTools are only supported in debug mode for web, see https://docs.flutter.dev/testing/build-modes#profile return residentRunner.flutterDevices - .where((FlutterDevice? device) => device?.targetPlatform.type != .web) + .where( + (FlutterDevice? device) => device?.targetPlatform != TargetPlatform.web_javascript, + ) .fold( true, (bool s, FlutterDevice? device) => diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index d039691be05ab..17ca7be7a166e 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -156,7 +156,7 @@ class HotRunner extends ResidentRunner { case 1: final Device device = flutterDevices.first.device!; final TargetPlatform targetPlatform = await device.targetPlatform; - _targetPlatformName = targetPlatform.devicePlatformName; + _targetPlatformName = targetPlatform.getName(); _targetPlatforms.add(targetPlatform); _sdkName = await device.sdkNameAndVersion; _emulator = await device.isLocalEmulator; @@ -1124,8 +1124,9 @@ class HotRunner extends ResidentRunner { uiIsolateId: view.uiIsolate!.id, viewId: view.id, windows: - (device.targetPlatform.type == .tester && globals.platform.isWindows) || - device.targetPlatform.type == .windows, + (device.targetPlatform == TargetPlatform.tester && globals.platform.isWindows) || + device.targetPlatform == TargetPlatform.windows_x64 || + device.targetPlatform == TargetPlatform.windows_arm64, ), ), ); diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index f7223995c0619..bbc03a2e9d864 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -2201,32 +2201,38 @@ mixin DeviceBasedDevelopmentArtifacts on FlutterCommand { // if none is supported @protected DevelopmentArtifact? artifactFromTargetPlatform(TargetPlatform targetPlatform) { - switch (targetPlatform.type) { - case .android: + switch (targetPlatform) { + case TargetPlatform.android: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: return DevelopmentArtifact.androidGenSnapshot; - case .web: + case TargetPlatform.web_javascript: return DevelopmentArtifact.web; - case .ios: + case TargetPlatform.ios: return DevelopmentArtifact.iOS; - case .macos: + case TargetPlatform.darwin: if (featureFlags.isMacOSEnabled) { return DevelopmentArtifact.macOS; } return null; - case .windows: + case TargetPlatform.windows_x64: + case TargetPlatform.windows_arm64: if (featureFlags.isWindowsEnabled) { return DevelopmentArtifact.windows; } return null; - case .linux: + case TargetPlatform.linux_x64: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_riscv64: if (featureFlags.isLinuxEnabled) { return DevelopmentArtifact.linux; } return null; - case .fuchsia: - case .tester: - case .custom: - case .unsupported: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.tester: + case TargetPlatform.unsupported: return null; } } diff --git a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart index efb1c97963468..5a31e32024443 100644 --- a/packages/flutter_tools/lib/src/test/flutter_web_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_web_platform.dart @@ -246,7 +246,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', @@ -260,7 +260,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', @@ -274,7 +274,7 @@ class FlutterWebPlatform extends PlatformPlugin { _fileSystem.path.join( _artifacts!.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'lib', 'dev_compiler', diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index c0e8000d12064..af01c969b26f5 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -554,7 +554,7 @@ class SpawnPlugin extends PlatformPlugin { final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Compile); final ResidentCompiler residentCompiler = residentCompilerFactory.create( - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: .tester, artifacts: globals.artifacts!, logger: globals.logger, processManager: globals.processManager, diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart index 45d5a919bb2f3..28cefab64e1f6 100644 --- a/packages/flutter_tools/lib/src/test/test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/test_compiler.dart @@ -201,7 +201,7 @@ class TestCompiler { fileSystem: globals.fs, shutdownHooks: globals.shutdownHooks, config: globals.config, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: .tester, ); return residentCompiler; } diff --git a/packages/flutter_tools/lib/src/test/web_test_compiler.dart b/packages/flutter_tools/lib/src/test/web_test_compiler.dart index 801d38529791c..b1dace1540957 100644 --- a/packages/flutter_tools/lib/src/test/web_test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/web_test_compiler.dart @@ -149,7 +149,7 @@ class WebTestCompiler { fileSystem: _fileSystem, shutdownHooks: _shutdownHooks, config: _config, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: .web_javascript, ); final CompilerOutput? output = await residentCompiler.recompile( @@ -204,7 +204,7 @@ class WebTestCompiler { final compilationArgs = [ _artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), 'compile', 'wasm', diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart index 306d548f532cd..f87c6573369a6 100644 --- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart +++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart @@ -94,7 +94,7 @@ class FlutterTesterDevice extends Device { bool get supportsFlavors => true; @override - Future get targetPlatform async => const TargetPlatform(.tester, .unknown); + Future get targetPlatform async => TargetPlatform.tester; @override Future get cpuArch async => CpuArch.unknown; @@ -152,7 +152,7 @@ class FlutterTesterDevice extends Device { buildInfo: buildInfo, mainPath: mainPath, applicationKernelFilePath: applicationKernelFilePath, - platform: const TargetPlatform(.tester, .unknown), + platform: TargetPlatform.tester, assetDirPath: assetDirectory.path, ); diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart index a99a63198e4ae..d6ea3be1b9ce8 100644 --- a/packages/flutter_tools/lib/src/web/web_device.dart +++ b/packages/flutter_tools/lib/src/web/web_device.dart @@ -167,6 +167,9 @@ abstract class ChromiumDevice extends WebDevice { return true; } + @override + Future get targetPlatform async => TargetPlatform.web_javascript; + @override Future uninstallApp(ApplicationPackage app, {String? userIdentifier}) async => true; @@ -466,6 +469,9 @@ class WebServerDevice extends WebDevice { return true; } + @override + Future get targetPlatform async => TargetPlatform.web_javascript; + @override Future uninstallApp(ApplicationPackage app, {String? userIdentifier}) async { return true; diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 62183da7c6833..70ae440fc1b51 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -170,12 +170,10 @@ Future buildWindows( } String getCmakeWindowsArch(TargetPlatform targetPlatform) { - if (targetPlatform.type != .windows) { - throw Exception('Unsupported target platform "$targetPlatform".'); - } - return switch (targetPlatform.cpuArch) { - .arm64 => 'ARM64', - _ => 'x64', + return switch (targetPlatform) { + TargetPlatform.windows_x64 => 'x64', + TargetPlatform.windows_arm64 => 'ARM64', + _ => throw Exception('Unsupported target platform "$targetPlatform".'), }; } diff --git a/packages/flutter_tools/lib/src/windows/windows_device.dart b/packages/flutter_tools/lib/src/windows/windows_device.dart index 3acc69df82476..e3014cf339161 100644 --- a/packages/flutter_tools/lib/src/windows/windows_device.dart +++ b/packages/flutter_tools/lib/src/windows/windows_device.dart @@ -38,9 +38,12 @@ class WindowsDevice extends DesktopDevice { @override String get name => 'Windows'; + @override + Future get targetPlatform async => _targetPlatform; + TargetPlatform get _targetPlatform => switch (_operatingSystemUtils.hostPlatform) { - HostPlatform.windows_arm64 => const TargetPlatform(.windows, .arm64), - _ => const TargetPlatform(.windows, .x64), + HostPlatform.windows_arm64 => TargetPlatform.windows_arm64, + _ => TargetPlatform.windows_x64, }; @override diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index fcb79e5ebee3a..1cc3967726777 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -1882,7 +1882,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get targetPlatformDisplayName async => 'android'; @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; @override DeviceConnectionInterface get connectionInterface => DeviceConnectionInterface.attached; @@ -1996,7 +1996,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { String get displayName => name; @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; @override final PlatformType platformType = PlatformType.ios; @@ -2075,7 +2075,7 @@ class FakeIOSSimulator extends Fake implements IOSSimulator { bool get ephemeral => true; @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; @override final PlatformType platformType = PlatformType.ios; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart index a927136422831..84ae4fca7785c 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_darwin_framework_test.dart @@ -442,7 +442,7 @@ void main() { // Mock engine artifacts. _TestArtifacts uses a string like this for getArtifactPath. memoryFileSystem - .directory('Artifact.flutterXcframework.ios.debug') + .directory('Artifact.flutterXcframework.TargetPlatform.ios.debug') .createSync(recursive: true); final Directory buildDir = @@ -528,7 +528,7 @@ void main() { // Mock engine artifacts memoryFileSystem - .directory('Artifact.flutterXcframework.ios.debug') + .directory('Artifact.flutterXcframework.TargetPlatform.ios.debug') .createSync(recursive: true); final Directory buildDir = diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart index aab69e37d7706..a2b47feabd5a7 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_swift_package_test.dart @@ -740,7 +740,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin-x64', + 'TargetPlatform': 'darwin', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'debug', 'DartObfuscation': 'false', @@ -848,7 +848,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin-x64', + 'TargetPlatform': 'darwin', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'release', 'DartObfuscation': 'false', @@ -974,7 +974,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'darwin-x64', + 'TargetPlatform': 'darwin', 'DarwinArchs': 'x86_64 arm64', 'BuildMode': 'release', 'DartObfuscation': 'false', @@ -1085,7 +1085,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1107,7 +1107,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1219,7 +1219,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -1241,7 +1241,7 @@ let package = Package( expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -2917,7 +2917,7 @@ public func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', @@ -2939,7 +2939,7 @@ public func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { expectedEngineVersion: _engineVersion, expectedDefines: { 'TargetFile': 'lib/main.dart', - 'TargetPlatform': 'ios-arm64', + 'TargetPlatform': 'ios', 'IosArchs': 'x86_64 arm64', 'SdkRoot': _iosSdkRoot, 'BuildMode': 'debug', diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index 18aeb31f75471..11c574670713a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -79,7 +79,7 @@ void main() { FakeCommand cmakeGenerationCommand({ void Function(List command)? onRun, String generator = _defaultGenerator, - TargetPlatform targetPlatform = const TargetPlatform(.windows, .x64), + TargetPlatform targetPlatform = TargetPlatform.windows_x64, }) { return FakeCommand( command: [ diff --git a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart index 0ce272cc1e260..541d2272e8ab3 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/daemon_test.dart @@ -577,10 +577,7 @@ void main() { ); expect(applicationPackageIdResponse.data['id'], 0); expect(applicationPackageFactory.applicationBinaryRequested!.basename, 'test_file'); - expect( - applicationPackageFactory.platformRequested, - const TargetPlatform(.android, .unknown), - ); + expect(applicationPackageFactory.platformRequested, TargetPlatform.android); final applicationPackageId = applicationPackageIdResponse.data['result'] as String?; // Try starting the app. @@ -1165,7 +1162,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get emulatorId async => 'device'; @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; @override Future get cpuArch async => CpuArch.armv7; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart index 4da698dffec12..c2a4f07f0448a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart @@ -114,148 +114,176 @@ void main() { }); group('doctor with fake validators', () { - testUsingContext('validate non-verbose output format for run without issues', () async { - expect(await FakeQuietDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '• No issues found!\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output format for run without issues', + () async { + expect(await FakeQuietDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '• No issues found!\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate non-verbose output format for run with crash', () async { - expect(await FakeCrashingDoctor(logger).diagnose(verbose: false), isFalse); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[☠] Crashing validator (the doctor check crashed)\n' - ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' - 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' - ' ✗ Bad state: fatal error\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output format for run with crash', + () async { + expect(await FakeCrashingDoctor(logger).diagnose(verbose: false), isFalse); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[☠] Crashing validator (the doctor check crashed)\n' + ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' + 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' + ' ✗ Bad state: fatal error\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); testUsingContext('validate verbose output format contains trace for run with crash', () async { expect(await FakeCrashingDoctor(logger).diagnose(), isFalse); expect(logger.statusText, contains('#0 CrashingValidator.validate')); }); - testUsingContext('validate tool exit when exceeding timeout', () async { - FakeAsync().run((FakeAsync time) { - final Doctor doctor = FakeAsyncStuckDoctor(logger); - doctor.diagnose(verbose: false); - time.elapse(const Duration(minutes: 5)); - time.flushMicrotasks(); - }); + testUsingContext( + 'validate tool exit when exceeding timeout', + () async { + FakeAsync().run((FakeAsync time) { + final Doctor doctor = FakeAsyncStuckDoctor(logger); + doctor.diagnose(verbose: false); + time.elapse(const Duration(minutes: 5)); + time.flushMicrotasks(); + }); - expect( - logger.statusText, - contains('Stuck validator that never completes exceeded maximum allowed duration of '), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + expect( + logger.statusText, + contains('Stuck validator that never completes exceeded maximum allowed duration of '), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate non-verbose output format for run with an async crash', () async { - final completer = Completer(); - await FakeAsync().run((FakeAsync time) { - unawaited( - FakeAsyncCrashingDoctor(time, logger).diagnose(verbose: false).then((bool r) { - expect(r, isFalse); - completer.complete(); - }), + testUsingContext( + 'validate non-verbose output format for run with an async crash', + () async { + final completer = Completer(); + await FakeAsync().run((FakeAsync time) { + unawaited( + FakeAsyncCrashingDoctor(time, logger).diagnose(verbose: false).then((bool r) { + expect(r, isFalse); + completer.complete(); + }), + ); + time.elapse(const Duration(seconds: 1)); + time.flushMicrotasks(); + return completer.future; + }); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '[☠] Async crashing validator (the doctor check crashed)\n' + ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' + 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' + ' ✗ Bad state: fatal error\n' + '[✓] Validators are fun (with statusInfo)\n' + '[✓] Four score and seven validators ago (with statusInfo)\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), ); - time.elapse(const Duration(seconds: 1)); - time.flushMicrotasks(); - return completer.future; - }); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '[☠] Async crashing validator (the doctor check crashed)\n' - ' ✗ Due to an error, the doctor check did not complete. If the error message below is not helpful, ' - 'please let us know about this issue at https://github.com/flutter/flutter/issues.\n' - ' ✗ Bad state: fatal error\n' - '[✓] Validators are fun (with statusInfo)\n' - '[✓] Four score and seven validators ago (with statusInfo)\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate non-verbose output format when only one category fails', () async { - expect(await FakeSinglePassingDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output format when only one category fails', + () async { + expect(await FakeSinglePassingDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate non-verbose output format for a passing run', () async { - expect(await FakePassingDoctor(logger).diagnose(verbose: false), isTrue); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with Errors\n' - ' ✗ An error message indicating partial installation\n' - ' ! Maybe a hint will help the user\n' - '[✓] Another Passing Validator (with statusInfo)\n' - '\n' - '! Doctor found issues in 2 categories.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output format for a passing run', + () async { + expect(await FakePassingDoctor(logger).diagnose(verbose: false), isTrue); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with Errors\n' + ' ✗ An error message indicating partial installation\n' + ' ! Maybe a hint will help the user\n' + '[✓] Another Passing Validator (with statusInfo)\n' + '\n' + '! Doctor found issues in 2 categories.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate non-verbose output format', () async { - expect(await FakeDoctor(logger).diagnose(verbose: false), isFalse); - expect( - logger.statusText, - equals( - 'Doctor summary (to see all details, run flutter doctor -v):\n' - '[✓] Passing Validator (with statusInfo)\n' - '[✗] Missing Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Not Available Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Partial Validator with only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with Errors\n' - ' ✗ An error message indicating partial installation\n' - ' ! Maybe a hint will help the user\n' - '\n' - '! Doctor found issues in 4 categories.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output format', + () async { + expect(await FakeDoctor(logger).diagnose(verbose: false), isFalse); + expect( + logger.statusText, + equals( + 'Doctor summary (to see all details, run flutter doctor -v):\n' + '[✓] Passing Validator (with statusInfo)\n' + '[✗] Missing Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Not Available Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Partial Validator with only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with Errors\n' + ' ✗ An error message indicating partial installation\n' + ' ! Maybe a hint will help the user\n' + '\n' + '! Doctor found issues in 4 categories.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); testUsingContext('validate verbose output format', () async { expect(await FakeDoctor(logger).diagnose(), isFalse); @@ -377,41 +405,45 @@ void main() { }); }); - testUsingContext('validate non-verbose output wrapping', () async { - final wrapLogger = BufferLogger.test( - outputPreferences: OutputPreferences(wrapText: true, wrapColumn: 30), - ); - expect(await FakeDoctor(wrapLogger).diagnose(verbose: false), isFalse); - expect( - wrapLogger.statusText, - equals( - 'Doctor summary (to see all\n' - 'details, run flutter doctor\n' - '-v):\n' - '[✓] Passing Validator (with\n' - ' statusInfo)\n' - '[✗] Missing Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Not Available Validator\n' - ' ✗ A useful error message\n' - ' ! A hint message\n' - '[!] Partial Validator with\n' - ' only a Hint\n' - ' ! There is a hint here\n' - '[!] Partial Validator with\n' - ' Errors\n' - ' ✗ An error message\n' - ' indicating partial\n' - ' installation\n' - ' ! Maybe a hint will help\n' - ' the user\n' - '\n' - '! Doctor found issues in 4\n' - ' categories.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate non-verbose output wrapping', + () async { + final wrapLogger = BufferLogger.test( + outputPreferences: OutputPreferences(wrapText: true, wrapColumn: 30), + ); + expect(await FakeDoctor(wrapLogger).diagnose(verbose: false), isFalse); + expect( + wrapLogger.statusText, + equals( + 'Doctor summary (to see all\n' + 'details, run flutter doctor\n' + '-v):\n' + '[✓] Passing Validator (with\n' + ' statusInfo)\n' + '[✗] Missing Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Not Available Validator\n' + ' ✗ A useful error message\n' + ' ! A hint message\n' + '[!] Partial Validator with\n' + ' only a Hint\n' + ' ! There is a hint here\n' + '[!] Partial Validator with\n' + ' Errors\n' + ' ✗ An error message\n' + ' indicating partial\n' + ' installation\n' + ' ! Maybe a hint will help\n' + ' the user\n' + '\n' + '! Doctor found issues in 4\n' + ' categories.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); testUsingContext('validate verbose output wrapping', () async { final wrapLogger = BufferLogger.test( @@ -462,38 +494,46 @@ void main() { }, overrides: {AnsiTerminal: () => FakeTerminal()}); group('doctor with grouped validators', () { - testUsingContext('validate diagnose combines validator output', () async { - expect(await FakeGroupedDoctor(logger).diagnose(), isTrue); - expect( - logger.statusText, - equals( - '[✓] Category 1 [0ms]\n' - ' • A helpful message\n' - ' • A helpful message\n' - '\n' - '[!] Category 2 [0ms]\n' - ' • A helpful message\n' - ' ✗ A useful error message\n' - '\n' - '! Doctor found issues in 1 category.\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate diagnose combines validator output', + () async { + expect(await FakeGroupedDoctor(logger).diagnose(), isTrue); + expect( + logger.statusText, + equals( + '[✓] Category 1 [0ms]\n' + ' • A helpful message\n' + ' • A helpful message\n' + '\n' + '[!] Category 2 [0ms]\n' + ' • A helpful message\n' + ' ✗ A useful error message\n' + '\n' + '! Doctor found issues in 1 category.\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate merging assigns statusInfo and title', () async { - // There are two subvalidators. Only the second contains statusInfo. - expect(await FakeGroupedDoctorWithStatus(logger).diagnose(), isTrue); - expect( - logger.statusText, - equals( - '[✓] First validator title (A status message) [0ms]\n' - ' • A helpful message\n' - ' • A different message\n' - '\n' - '• No issues found!\n', - ), - ); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate merging assigns statusInfo and title', + () async { + // There are two subvalidators. Only the second contains statusInfo. + expect(await FakeGroupedDoctorWithStatus(logger).diagnose(), isTrue); + expect( + logger.statusText, + equals( + '[✓] First validator title (A status message) [0ms]\n' + ' • A helpful message\n' + ' • A different message\n' + '\n' + '• No issues found!\n', + ), + ); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); }); group('grouped validator merging results', () { @@ -501,50 +541,86 @@ void main() { final partial = PartialGroupedValidator('Category'); final missing = MissingGroupedValidator('Category'); - testUsingContext('validate installed + installed = installed', () async { - expect(await FakeSmallGroupDoctor(logger, installed, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[✓]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate installed + installed = installed', + () async { + expect(await FakeSmallGroupDoctor(logger, installed, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[✓]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate installed + partial = partial', () async { - expect(await FakeSmallGroupDoctor(logger, installed, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate installed + partial = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, installed, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate installed + missing = partial', () async { - expect(await FakeSmallGroupDoctor(logger, installed, missing).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate installed + missing = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, installed, missing).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate partial + installed = partial', () async { - expect(await FakeSmallGroupDoctor(logger, partial, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate partial + installed = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, partial, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate partial + partial = partial', () async { - expect(await FakeSmallGroupDoctor(logger, partial, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate partial + partial = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, partial, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate partial + missing = partial', () async { - expect(await FakeSmallGroupDoctor(logger, partial, missing).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate partial + missing = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, partial, missing).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate missing + installed = partial', () async { - expect(await FakeSmallGroupDoctor(logger, missing, installed).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate missing + installed = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, missing, installed).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate missing + partial = partial', () async { - expect(await FakeSmallGroupDoctor(logger, missing, partial).diagnose(), isTrue); - expect(logger.statusText, startsWith('[!]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate missing + partial = partial', + () async { + expect(await FakeSmallGroupDoctor(logger, missing, partial).diagnose(), isTrue); + expect(logger.statusText, startsWith('[!]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); - testUsingContext('validate missing + missing = missing', () async { - expect(await FakeSmallGroupDoctor(logger, missing, missing).diagnose(), isFalse); - expect(logger.statusText, startsWith('[✗]')); - }, overrides: {AnsiTerminal: () => FakeTerminal()}); + testUsingContext( + 'validate missing + missing = missing', + () async { + expect(await FakeSmallGroupDoctor(logger, missing, missing).diagnose(), isFalse); + expect(logger.statusText, startsWith('[✗]')); + }, + overrides: {AnsiTerminal: () => FakeTerminal()}, + ); }); testUsingContext( @@ -562,13 +638,17 @@ void main() { }, ); - testUsingContext('CustomDevicesWorkflow is a part of validator workflows if enabled', () async { - final List workflows = DoctorValidatorsProvider.test( - featureFlags: TestFeatureFlags(areCustomDevicesEnabled: true), - platform: FakePlatform(), - ).workflows; - expect(workflows, contains(isA())); - }, overrides: {FileSystem: () => fs, ProcessManager: () => fakeProcessManager}); + testUsingContext( + 'CustomDevicesWorkflow is a part of validator workflows if enabled', + () async { + final List workflows = DoctorValidatorsProvider.test( + featureFlags: TestFeatureFlags(areCustomDevicesEnabled: true), + platform: FakePlatform(), + ).workflows; + expect(workflows, contains(isA())); + }, + overrides: {FileSystem: () => fs, ProcessManager: () => fakeProcessManager}, + ); group('FlutterValidator', () { late FakeFlutterVersion initialVersion; @@ -634,10 +714,14 @@ void main() { ); }); - testUsingContext('ensure fake is being used and initialized', () { - expect(fakeAnalytics.sentEvents.length, 0); - expect(fakeAnalytics.okToSend, true); - }, overrides: {Analytics: () => fakeAnalytics}); + testUsingContext( + 'ensure fake is being used and initialized', + () { + expect(fakeAnalytics.sentEvents.length, 0); + expect(fakeAnalytics.okToSend, true); + }, + overrides: {Analytics: () => fakeAnalytics}, + ); testUsingContext( 'contains installed', @@ -795,22 +879,26 @@ void main() { }, ); - testUsingContext('grouped validator subresult and subvalidators different lengths', () async { - final fakeDoctor = FakeGroupedDoctorWithCrash(logger, clock: fakeSystemClock); - await fakeDoctor.diagnose(verbose: false); - - expect(fakeDoctor.validators, hasLength(1)); - expect(fakeDoctor.validators.first.runtimeType == FakeGroupedValidatorWithCrash, true); - expect(fakeAnalytics.sentEvents, hasLength(0)); - - // Attempt to send a random event to ensure that the - // analytics package is still working, despite not sending - // above (as expected) - final testEvent = Event.analyticsCollectionEnabled(status: true); - fakeAnalytics.send(testEvent); - expect(fakeAnalytics.sentEvents, hasLength(1)); - expect(fakeAnalytics.sentEvents, contains(testEvent)); - }, overrides: {Analytics: () => fakeAnalytics}); + testUsingContext( + 'grouped validator subresult and subvalidators different lengths', + () async { + final fakeDoctor = FakeGroupedDoctorWithCrash(logger, clock: fakeSystemClock); + await fakeDoctor.diagnose(verbose: false); + + expect(fakeDoctor.validators, hasLength(1)); + expect(fakeDoctor.validators.first.runtimeType == FakeGroupedValidatorWithCrash, true); + expect(fakeAnalytics.sentEvents, hasLength(0)); + + // Attempt to send a random event to ensure that the + // analytics package is still working, despite not sending + // above (as expected) + final testEvent = Event.analyticsCollectionEnabled(status: true); + fakeAnalytics.send(testEvent); + expect(fakeAnalytics.sentEvents, hasLength(1)); + expect(fakeAnalytics.sentEvents, contains(testEvent)); + }, + overrides: {Analytics: () => fakeAnalytics}, + ); testUsingContext('sending events can be skipped', () async { await FakePassingDoctor(logger).diagnose(verbose: false, sendEvent: false); @@ -1238,8 +1326,7 @@ class FakeDevice extends Fake implements Device { Future get sdkNameAndVersion async => '1.2.3'; @override - Future get targetPlatform => - Future.value(const TargetPlatform(.android, .unknown)); + Future get targetPlatform => Future.value(TargetPlatform.android); } class FakeTerminal extends Fake implements AnsiTerminal { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart index fb15a0167c6cc..2271447a6d644 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -935,7 +935,7 @@ class ScreenshotDevice extends Fake implements Device { final id = 'fake_device'; @override - Future get targetPlatform async => const TargetPlatform(.android, .unknown); + Future get targetPlatform async => TargetPlatform.android; @override bool supportsScreenshot = true; @@ -1130,7 +1130,7 @@ class FakeIosDevice extends Fake implements IOSDevice { bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; } class FakeChromiumDriveDevice extends Fake implements ChromiumDevice { @@ -1162,7 +1162,7 @@ class FakeChromiumDriveDevice extends Fake implements ChromiumDevice { Future get sdkNameAndVersion async => 'Google Chrome 0.0'; @override - Future get targetPlatform async => const TargetPlatform(.web, .unknown); + Future get targetPlatform async => TargetPlatform.web_javascript; @override DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart index 3244e0b06f574..f953604c00af4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/install_test.dart @@ -180,7 +180,7 @@ class FakeAndroidApk extends Fake implements AndroidApk {} class FakeIOSDevice extends Fake implements IOSDevice { @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; @override Future isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false; @@ -194,7 +194,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { class FakeAndroidDevice extends Fake implements AndroidDevice { @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; @override Future isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart index 1ad4ee46c751a..e9b100fb05c26 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/proxied_devices_test.dart @@ -169,7 +169,7 @@ void main() { applicationPackageFactory.applicationBinaryRequested!.readAsStringSync(), 'dummy content', ); - expect(applicationPackageFactory.platformRequested, const TargetPlatform(.android, .armv7)); + expect(applicationPackageFactory.platformRequested, TargetPlatform.android_arm); expect(fakeDevice.startAppPackage, applicationPackage); @@ -254,7 +254,7 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { Future get emulatorId async => 'device'; @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; @override Future get cpuArch async => CpuArch.arm64; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index c9717c0639b02..e096756bb5679 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -286,7 +286,7 @@ void main() { () async { final command = RunCommand(); final mockDevice = FakeDevice( - targetPlatform: const TargetPlatform(.android, .armv7), + targetPlatform: TargetPlatform.android_arm, isLocalEmulator: true, sdkNameAndVersion: 'api-14', isSupported: false, @@ -545,7 +545,7 @@ void main() { final command = RunCommand(); final device = FakeDevice( platformType: PlatformType.web, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); testDeviceManager.devices = [device]; @@ -691,7 +691,7 @@ void main() { 'should only request artifacts corresponding to connected devices', () async { testDeviceManager.devices = [ - FakeDevice(targetPlatform: const TargetPlatform(.android, .armv7)), + FakeDevice(targetPlatform: TargetPlatform.android_arm), ]; expect( @@ -714,7 +714,7 @@ void main() { testDeviceManager.devices = [ FakeDevice(), - FakeDevice(targetPlatform: const TargetPlatform(.android, .armv7)), + FakeDevice(targetPlatform: TargetPlatform.android_arm), ]; expect( @@ -727,7 +727,7 @@ void main() { ); testDeviceManager.devices = [ - FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)), + FakeDevice(targetPlatform: TargetPlatform.web_javascript), ]; expect( @@ -752,7 +752,7 @@ void main() { () async { final devices = [ FakeDevice( - targetPlatform: const TargetPlatform(.android, .armv7), + targetPlatform: TargetPlatform.android_arm, platformType: PlatformType.android, ), ]; @@ -986,7 +986,7 @@ void main() { final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); testDeviceManager.devices = [device]; }); @@ -1160,7 +1160,7 @@ void main() { final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); testDeviceManager.devices = [device]; }); @@ -1425,7 +1425,7 @@ server: final device = FakeDevice( isLocalEmulator: true, platformType: PlatformType.web, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); testDeviceManager.devices = [device]; }); @@ -1931,7 +1931,7 @@ class TestDeviceManager extends DeviceManager { class FakeDevice extends Fake implements Device { FakeDevice({ bool isLocalEmulator = false, - TargetPlatform targetPlatform = const TargetPlatform(.ios, .arm64), + TargetPlatform targetPlatform = TargetPlatform.ios, String sdkNameAndVersion = '', PlatformType platformType = PlatformType.ios, bool isSupported = true, @@ -1999,7 +1999,7 @@ class FakeDevice extends Fake implements Device { Future get sdkNameAndVersion => Future.value(_sdkNameAndVersion); @override - Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; + Future get targetPlatformDisplayName async => (await targetPlatform).getName(); @override DeviceLogReader getLogReader({ApplicationPackage? app, bool includePastLogs = false}) { @@ -2086,7 +2086,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { bool get isWirelesslyConnected => connectionInterface == DeviceConnectionInterface.wireless; @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; } class TestRunCommandForUsageValues extends RunCommand { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart index d8d48d8e263ec..76a40ddf8dfc0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/screenshot_command_test.dart @@ -147,45 +147,53 @@ void main() { testDeviceManager = _TestDeviceManager(logger: BufferLogger.test()); }); - testUsingContext('should not throw for a single device', () async { - final command = ScreenshotCommand(fs: MemoryFileSystem.test()); - - final deviceUnsupportedForProject = _ScreenshotDevice( - id: '123', - name: 'Device 1', - isSupportedForProject: false, - ); - - testDeviceManager.devices = [deviceUnsupportedForProject]; - - await createTestCommandRunner(command).run(['screenshot']); - }, overrides: {DeviceManager: () => testDeviceManager}); - - testUsingContext('should tool exit for multiple devices', () async { - final command = ScreenshotCommand(fs: MemoryFileSystem.test()); - - final devicesUnsupportedForProject = <_ScreenshotDevice>[ - _ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false), - _ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false), - ]; - - testDeviceManager.devices = devicesUnsupportedForProject; - - await expectLater( - () => createTestCommandRunner(command).run(['screenshot']), - throwsToolExit(message: 'Must have a connected device for screenshot type device'), - ); - - expect( - testLogger.statusText, - contains(''' + testUsingContext( + 'should not throw for a single device', + () async { + final command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final deviceUnsupportedForProject = _ScreenshotDevice( + id: '123', + name: 'Device 1', + isSupportedForProject: false, + ); + + testDeviceManager.devices = [deviceUnsupportedForProject]; + + await createTestCommandRunner(command).run(['screenshot']); + }, + overrides: {DeviceManager: () => testDeviceManager}, + ); + + testUsingContext( + 'should tool exit for multiple devices', + () async { + final command = ScreenshotCommand(fs: MemoryFileSystem.test()); + + final devicesUnsupportedForProject = <_ScreenshotDevice>[ + _ScreenshotDevice(id: '123', name: 'Device 1', isSupportedForProject: false), + _ScreenshotDevice(id: '456', name: 'Device 2', isSupportedForProject: false), + ]; + + testDeviceManager.devices = devicesUnsupportedForProject; + + await expectLater( + () => createTestCommandRunner(command).run(['screenshot']), + throwsToolExit(message: 'Must have a connected device for screenshot type device'), + ); + + expect( + testLogger.statusText, + contains(''' More than one device connected; please specify a device with the '-d ' flag, or use '-d all' to act on all devices. Device 1 (mobile) • 123 • android • 1.2.3 Device 2 (mobile) • 456 • android • 1.2.3 '''), - ); - }, overrides: {DeviceManager: () => testDeviceManager}); + ); + }, + overrides: {DeviceManager: () => testDeviceManager}, + ); }); } @@ -234,8 +242,7 @@ class _ScreenshotDevice extends Fake implements Device { Future get sdkNameAndVersion async => '1.2.3'; @override - Future get targetPlatform => - Future.value(const TargetPlatform(.android, .unknown)); + Future get targetPlatform => Future.value(TargetPlatform.android); @override Future get isLocalEmulator async => false; diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart index adc20ec9e8099..90b23c385699e 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart @@ -110,7 +110,7 @@ class FakeAnalysisServer extends Fake implements AnalysisServer { class FakeGoogleChromeDevice extends Fake implements GoogleChromeDevice { @override - Future get targetPlatform async => const TargetPlatform(.web, .unknown); + Future get targetPlatform async => TargetPlatform.web_javascript; @override PlatformType? get platformType => PlatformType.web; @@ -121,7 +121,7 @@ class FakeGoogleChromeDevice extends Fake implements GoogleChromeDevice { class FakeMicrosoftEdgeDevice extends Fake implements MicrosoftEdgeDevice { @override - Future get targetPlatform async => const TargetPlatform(.web, .unknown); + Future get targetPlatform async => TargetPlatform.web_javascript; @override PlatformType? get platformType => PlatformType.web; @@ -132,7 +132,7 @@ class FakeMicrosoftEdgeDevice extends Fake implements MicrosoftEdgeDevice { class FakeCustomBrowserDevice extends Fake implements ChromiumDevice { @override - Future get targetPlatform async => const TargetPlatform(.web, .unknown); + Future get targetPlatform async => TargetPlatform.web_javascript; @override PlatformType? get platformType => PlatformType.web; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart index fe914914bb75d..460cd8e772b98 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_start_test.dart @@ -49,9 +49,9 @@ void main() { }); for (final targetPlatform in [ - const TargetPlatform(.android, .armv7), - const TargetPlatform(.android, .arm64), - const TargetPlatform(.android, .x64), + TargetPlatform.android_arm, + TargetPlatform.android_arm64, + TargetPlatform.android_x64, ]) { testWithoutContext('AndroidDevice.startApp allows release builds on $targetPlatform', () async { final String arch = getCpuArchForName(targetPlatform.getName()).androidArchName; diff --git a/packages/flutter_tools/test/general.shard/android/android_device_test.dart b/packages/flutter_tools/test/general.shard/android/android_device_test.dart index eff4521548b90..160786caf8566 100644 --- a/packages/flutter_tools/test/general.shard/android/android_device_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_device_test.dart @@ -77,11 +77,11 @@ void main() { 'abi and abiList', () async { // The format is [ABI, ABI list]: expected target platform. final values = , TargetPlatform>{ - ['x86_64', 'unknown']: const TargetPlatform(.android, .x64), - ['armeabi-v7a', 'unknown']: const TargetPlatform(.android, .armv7), - ['arm64-v8a', 'arm64-v8a,']: const TargetPlatform(.android, .arm64), + ['x86_64', 'unknown']: TargetPlatform.android_x64, + ['armeabi-v7a', 'unknown']: TargetPlatform.android_arm, + ['arm64-v8a', 'arm64-v8a,']: TargetPlatform.android_arm64, // The Kindle Fire runs 32 bit apps on 64 bit hardware. - ['arm64-v8a', 'arm']: const TargetPlatform(.android, .armv7), + ['arm64-v8a', 'arm']: TargetPlatform.android_arm, }; for (final MapEntry, TargetPlatform> entry in values.entries) { diff --git a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart index 9b3948004ee74..e754dcbd40017 100644 --- a/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_emulator_test.dart @@ -7,8 +7,8 @@ import 'dart:async'; import 'package:flutter_tools/src/android/android_emulator.dart'; import 'package:flutter_tools/src/android/android_sdk.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/device.dart'; import 'package:test/fake.dart'; diff --git a/packages/flutter_tools/test/general.shard/application_package_test.dart b/packages/flutter_tools/test/general.shard/application_package_test.dart index 7237025027599..d34273ab401bc 100644 --- a/packages/flutter_tools/test/general.shard/application_package_test.dart +++ b/packages/flutter_tools/test/general.shard/application_package_test.dart @@ -88,7 +88,7 @@ void main() { ); await ApplicationPackageFactory.instance!.getPackageForPlatform( - const TargetPlatform(.android, .armv7), + TargetPlatform.android_arm, applicationBinary: apkFile, ); final logger = BufferLogger.test(); @@ -158,7 +158,7 @@ void main() { ); await ApplicationPackageFactory.instance!.getPackageForPlatform( - const TargetPlatform(.android, .armv7), + TargetPlatform.android_arm, applicationBinary: apkFile, ); final logger = BufferLogger.test(); @@ -214,10 +214,7 @@ void main() { ); final ApplicationPackage applicationPackage = (await ApplicationPackageFactory.instance! - .getPackageForPlatform( - const TargetPlatform(.android, .armv7), - applicationBinary: apkFile, - ))!; + .getPackageForPlatform(TargetPlatform.android_arm, applicationBinary: apkFile))!; expect(applicationPackage.name, 'app-debug.apk'); expect(applicationPackage, isA()); expect( @@ -245,7 +242,7 @@ void main() { gradleWrapperDir.childFile('gradlew.bat').writeAsStringSync('irrelevant'); await ApplicationPackageFactory.instance!.getPackageForPlatform( - const TargetPlatform(.android, .armv7), + TargetPlatform.android_arm, applicationBinary: globals.fs.file('app-debug.apk'), ); expect(fakeProcessManager, hasNoRemainingExpectations); @@ -257,9 +254,7 @@ void main() { final AndroidSdkVersion sdkVersion = FakeAndroidSdkVersion(); sdk.latestVersion = sdkVersion; - await ApplicationPackageFactory.instance!.getPackageForPlatform( - const TargetPlatform(.android, .armv7), - ); + await ApplicationPackageFactory.instance!.getPackageForPlatform(TargetPlatform.android_arm); expect(fakeProcessManager, hasNoRemainingExpectations); }, overrides: overrides, diff --git a/packages/flutter_tools/test/general.shard/artifacts_test.dart b/packages/flutter_tools/test/general.shard/artifacts_test.dart index 400539586a5af..46cacd14758d2 100644 --- a/packages/flutter_tools/test/general.shard/artifacts_test.dart +++ b/packages/flutter_tools/test/general.shard/artifacts_test.dart @@ -44,7 +44,7 @@ void main() { testWithoutContext('getArtifactPath', () { final String xcframeworkPath = artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ); expect( @@ -62,7 +62,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -72,7 +72,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -93,7 +93,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -101,7 +101,7 @@ void main() { ); final String actualReleaseFrameworkArtifact = artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.physical, ); @@ -114,7 +114,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ), fileSystem.path.join( @@ -140,10 +140,7 @@ void main() { ), ); expect( - artifacts.getArtifactPath( - Artifact.flutterTester, - platform: const TargetPlatform(.linux, .arm64), - ), + artifacts.getArtifactPath(Artifact.flutterTester, platform: TargetPlatform.linux_arm64), fileSystem.path.join( 'root', 'bin', @@ -192,7 +189,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), xcframeworkPath, @@ -204,7 +201,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), throwsToolExit(message: 'No xcframework found at $xcframeworkPath.'), @@ -217,7 +214,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), throwsToolExit(message: 'No macOS frameworks found in $xcframeworkPath'), @@ -230,7 +227,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), fileSystem.path.join(xcframeworkPath, 'macos-arm64_x86_64', 'FlutterMacOS.framework'), @@ -266,36 +263,9 @@ void main() { ); testWithoutContext('getEngineType', () { - expect( - artifacts.getEngineType(const TargetPlatform(.android, .armv7), BuildMode.debug), - 'android-arm', - ); - expect( - artifacts.getEngineType(const TargetPlatform(.ios, .arm64), BuildMode.release), - 'ios-release', - ); - expect(artifacts.getEngineType(const TargetPlatform(.macos, .x64)), 'darwin-x64'); - }); - - testWithoutContext('getArtifactPath resolves a generic Android target to arm64 for ' - 'architecture-independent artifacts', () { - // The Dart kernel / patched SDK is architecture independent, so the - // build system requests it with a generic Android target whose CPU - // architecture is unknown. This must not throw and should resolve to - // the same path as an explicit arm64 target. Regression test for the - // assertion crash introduced by the PlatformType + CpuArch refactor. - expect( - artifacts.getArtifactPath( - Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .unknown), - mode: BuildMode.release, - ), - artifacts.getArtifactPath( - Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .arm64), - mode: BuildMode.release, - ), - ); + expect(artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), 'android-arm'); + expect(artifacts.getEngineType(TargetPlatform.ios, BuildMode.release), 'ios-release'); + expect(artifacts.getEngineType(TargetPlatform.darwin), 'darwin-x64'); }); testWithoutContext( @@ -365,7 +335,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSXcframework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), xcframeworkPath, @@ -377,7 +347,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), throwsToolExit( @@ -394,7 +364,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), throwsToolExit( @@ -419,7 +389,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterMacOSFramework, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), fileSystem.path.join(xcframeworkPath, 'macos-arm64_x86_64', 'FlutterMacOS.framework'), @@ -434,7 +404,7 @@ void main() { testWithoutContext('getArtifactPath', () { final String xcframeworkPath = artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ); expect( @@ -444,7 +414,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -456,7 +426,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -485,7 +455,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.simulator, ), @@ -494,7 +464,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterFramework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, environmentType: EnvironmentType.physical, ), @@ -503,7 +473,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.flutterXcframework, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ), fileSystem.path.join('/out', 'android_debug_unopt', 'Flutter.xcframework'), @@ -602,21 +572,21 @@ void main() { expect( () => webArtifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), throwsToolExit(message: failureMessage), ); expect( () => webArtifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), throwsToolExit(message: failureMessage), ); expect( () => webArtifacts.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), throwsToolExit(message: failureMessage), ); @@ -632,7 +602,7 @@ void main() { expect( webArtifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join( '/flutter', @@ -647,21 +617,21 @@ void main() { expect( webArtifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join('/flutter', 'prebuilts', 'linux-x64', 'dart-sdk'), ); expect( webArtifacts.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join('/flutter', 'prebuilts', 'linux-x64', 'dart-sdk', 'bin', 'dart'), ); expect( webArtifacts.getArtifactPath( Artifact.engineDartAotRuntime, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join( '/flutter', @@ -684,7 +654,7 @@ void main() { expect( () => artifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), throwsToolExit(message: failureMessage), ); @@ -699,7 +669,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartSdkPath, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join('/out', 'host_debug_unopt', 'dart-sdk'), ); @@ -707,14 +677,11 @@ void main() { testWithoutContext('getEngineType', () { expect( - artifacts.getEngineType(const TargetPlatform(.android, .armv7), BuildMode.debug), - 'android_debug_unopt', - ); - expect( - artifacts.getEngineType(const TargetPlatform(.ios, .arm64), BuildMode.release), + artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug), 'android_debug_unopt', ); - expect(artifacts.getEngineType(const TargetPlatform(.macos, .x64)), 'android_debug_unopt'); + expect(artifacts.getEngineType(TargetPlatform.ios, BuildMode.release), 'android_debug_unopt'); + expect(artifacts.getEngineType(TargetPlatform.darwin), 'android_debug_unopt'); }); testWithoutContext('Looks up dart.exe on windows platforms', () async { @@ -797,7 +764,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join('/flutter', 'prebuilts', 'windows-x64', 'dart-sdk', 'bin', 'dart.exe'), ); @@ -835,7 +802,7 @@ void main() { expect( artifacts.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), fileSystem.path.join('/flutter', 'prebuilts', 'macos-x64', 'dart-sdk', 'bin', 'dart'), ); diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart index fd4a60443758e..ac44f4319ad6a 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_flavors_test.dart @@ -38,7 +38,7 @@ void main() { packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), flavor: flavor, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); return bundle; } diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart index 8fd2a465042b8..e4f3c165477da 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart @@ -65,7 +65,7 @@ $fontsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); for (final packageName in packages) { @@ -120,7 +120,7 @@ $fontsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart index 412c47fc46cc0..d1b2c97ac8509 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart @@ -87,7 +87,7 @@ $assetsSection await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flavor: flavor, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); for (final packageName in packages) { @@ -153,7 +153,7 @@ $assetsSection expect( () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), throwsToolExit(message: 'resolves to a location outside the package directory'), ); @@ -178,7 +178,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -209,7 +209,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -711,7 +711,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( @@ -803,7 +803,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); }, overrides: { 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 1bbc7df16c0ef..e332c6c2b710d 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -47,7 +47,7 @@ void main() { expect( await ab.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -70,7 +70,7 @@ void main() { final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(bundle.entries.keys, unorderedEquals(['AssetManifest.bin'])); const expectedBinAssetManifest = {}; @@ -120,7 +120,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( @@ -163,7 +163,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -182,7 +182,7 @@ flutter: expect(bundle.needsBuild(), true); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -223,7 +223,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -254,7 +254,7 @@ name: my_app''') expect(bundle.needsBuild(), true); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -295,7 +295,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -350,7 +350,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -401,7 +401,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -462,7 +462,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( bundle.entries.keys, @@ -486,7 +486,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', deferredComponentsEnabled: true, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( @@ -540,7 +540,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), throwsToolExit( message: @@ -580,7 +580,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), throwsToolExit( message: @@ -625,7 +625,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(bundle.entries['my-asset.txt']!.content.isModified, isTrue); @@ -634,7 +634,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(bundle.entries['my-asset.txt']!.content.isModified, isFalse); @@ -652,7 +652,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(bundle.entries['my-asset.txt']!.content.isModified, isTrue); @@ -682,7 +682,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); expect( @@ -721,7 +721,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); expect( @@ -774,7 +774,7 @@ flutter: await writeBundle( directory, const {}, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -803,7 +803,7 @@ assets: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final AssetBundleEntry? fontManifest = bundle.entries['FontManifest.json']; @@ -811,7 +811,7 @@ assets: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(fontManifest, bundle.entries['FontManifest.json']); @@ -843,7 +843,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -878,7 +878,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -931,7 +931,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -939,7 +939,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -994,7 +994,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ), 0, ); @@ -1002,7 +1002,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -1156,7 +1156,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ), 0, ); @@ -1164,7 +1164,7 @@ flutter: await writeBundle( output, bundle.entries, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, impellerStatus: ImpellerStatus.disabled, processManager: globals.processManager, fileSystem: globals.fs, @@ -1215,7 +1215,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -1258,7 +1258,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect( @@ -1315,7 +1315,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 1, ); @@ -1350,7 +1350,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 1, ); @@ -1395,7 +1395,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -1437,7 +1437,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -1487,7 +1487,7 @@ flutter: expect( await bundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), 0, ); @@ -1529,7 +1529,7 @@ flutter: () => bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ), throwsToolExit( message: diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart index 3b58ecb7b5b74..91f24c632dcdf 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart @@ -87,7 +87,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -135,7 +135,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -179,7 +179,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final Map smcBinManifest = await extractAssetManifestSmcBinFromBundle( @@ -219,7 +219,7 @@ ${assets.map((String entry) => ' - $entry').join('\n')} await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final expectedManifest = >>{ @@ -287,7 +287,7 @@ flutter: await bundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final expectedAssetManifest = >>{ diff --git a/packages/flutter_tools/test/general.shard/asset_test.dart b/packages/flutter_tools/test/general.shard/asset_test.dart index 28f7505a45566..eb0e666d4d79f 100644 --- a/packages/flutter_tools/test/general.shard/asset_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_test.dart @@ -96,7 +96,7 @@ dependencies: packageConfigPath: packageConfigPath, manifestPath: manifestPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.directory('main')), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(assetBundle.entries, contains('FontManifest.json')); @@ -255,7 +255,7 @@ flutter: packageConfigPath: packageConfigPath, manifestPath: manifestPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.directory('main')), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(assetBundle.entries, contains('FontManifest.json')); @@ -300,7 +300,7 @@ flutter: manifestPath: manifestPath, // file doesn't exist packageConfigPath: packageConfigPath, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.file(manifestPath).parent), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(assetBundle.wasBuiltOnce(), true); @@ -343,7 +343,7 @@ flutter: await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.android, .armv7), + targetPlatform: TargetPlatform.android_arm, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); @@ -387,7 +387,7 @@ flutter: await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), ); @@ -416,7 +416,7 @@ flutter: final int result = await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(result, isNot(0)); expect( @@ -446,7 +446,7 @@ flutter: final int result = await assetBundle.build( packageConfigPath: '.dart_tool/package_config.json', flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); expect(result, isNot(0)); expect( 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 bb47b4200f27d..2f88ba4a34670 100644 --- a/packages/flutter_tools/test/general.shard/base/build_test.dart +++ b/packages/flutter_tools/test/general.shard/base/build_test.dart @@ -43,7 +43,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .x64), + platform: TargetPlatform.android_x64, mode: BuildMode.release, ), '--additional_arg', @@ -52,7 +52,7 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(const TargetPlatform(.android, .x64), BuildMode.release), + snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), additionalArgs: ['--additional_arg'], ); expect(result, 0); @@ -61,7 +61,7 @@ void main() { testWithoutContext('iOS arm64', () async { final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ); processManager.addCommand( @@ -69,7 +69,8 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(const TargetPlatform(.ios, .arm64), BuildMode.release), + snapshotType: SnapshotType(TargetPlatform.ios, BuildMode.release), + cpuArch: CpuArch.arm64, additionalArgs: ['--additional_arg'], ); expect(result, 0); @@ -81,7 +82,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .x64), + platform: TargetPlatform.android_x64, mode: BuildMode.release, ), '--strip', @@ -91,7 +92,7 @@ void main() { ); final int result = await genSnapshot.run( - snapshotType: SnapshotType(const TargetPlatform(.android, .x64), BuildMode.release), + snapshotType: SnapshotType(TargetPlatform.android_x64, BuildMode.release), additionalArgs: ['--strip'], ); @@ -128,7 +129,8 @@ void main() { expect( await snapshotter.build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', buildMode: BuildMode.debug, mainPath: 'main.dill', @@ -144,7 +146,7 @@ void main() { expect( await snapshotter.build( - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, buildMode: BuildMode.debug, mainPath: 'main.dill', outputPath: outputPath, @@ -159,7 +161,7 @@ void main() { expect( await snapshotter.build( - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, buildMode: BuildMode.debug, mainPath: 'main.dill', outputPath: outputPath, @@ -174,7 +176,7 @@ void main() { final String debugPath = fileSystem.path.join('foo', 'app.ios-arm64.symbols'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.profile, ); processManager.addCommands([ @@ -218,10 +220,11 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', splitDebugInfo: 'foo', dartObfuscation: false, @@ -235,7 +238,7 @@ void main() { final String outputPath = fileSystem.path.join('build', 'foo'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.profile, ); processManager.addCommands([ @@ -277,10 +280,11 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildMode: BuildMode.profile, mainPath: 'main.dill', outputPath: outputPath, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: true, ); @@ -293,7 +297,7 @@ void main() { final String outputPath = fileSystem.path.join('build', 'foo'); final String genSnapshotPath = artifacts.getArtifactPath( Artifact.genSnapshotArm64, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.release, ); processManager.addCommands([ @@ -334,10 +338,11 @@ void main() { ]); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, + cpuArch: CpuArch.arm64, sdkRoot: 'path/to/sdk', dartObfuscation: false, ); @@ -353,7 +358,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.release, ), '--deterministic', @@ -367,7 +372,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -386,7 +391,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.release, ), '--deterministic', @@ -403,7 +408,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -422,7 +427,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.release, ), '--deterministic', @@ -437,7 +442,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -457,7 +462,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.release, ), '--deterministic', @@ -471,7 +476,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -491,7 +496,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -503,7 +508,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, @@ -521,7 +526,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -533,7 +538,7 @@ void main() { ); final int genSnapshotExitCode = await snapshotter.build( - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, buildMode: BuildMode.release, mainPath: 'main.dill', outputPath: outputPath, diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart index ad6ffe52076c0..57fcd7f4dad76 100644 --- a/packages/flutter_tools/test/general.shard/build_info_test.dart +++ b/packages/flutter_tools/test/general.shard/build_info_test.dart @@ -21,47 +21,51 @@ void main() { group('Validate build number', () { testWithoutContext('CFBundleVersion for iOS', () async { - String? buildName = validatedBuildNumberForPlatform(PlatformType.ios, 'xyz', logger); + String? buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, 'xyz', logger); expect(buildName, isNull); - buildName = validatedBuildNumberForPlatform(PlatformType.ios, '0.0.1', logger); + buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '0.0.1', logger); expect(buildName, '0.0.1'); - buildName = validatedBuildNumberForPlatform(PlatformType.ios, '123.xyz', logger); + buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '123.xyz', logger); expect(buildName, '123'); - buildName = validatedBuildNumberForPlatform(PlatformType.ios, '123.456.xyz', logger); + buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '123.456.xyz', logger); expect(buildName, '123.456'); }); testWithoutContext('versionCode for Android', () async { String? buildName = validatedBuildNumberForPlatform( - PlatformType.android, + TargetPlatform.android_arm, '123.abc+-', logger, ); expect(buildName, '123'); - buildName = validatedBuildNumberForPlatform(PlatformType.android, 'abc', logger); + buildName = validatedBuildNumberForPlatform(TargetPlatform.android_arm, 'abc', logger); expect(buildName, '1'); }); }); group('Validate build name', () { testWithoutContext('CFBundleShortVersionString for iOS', () async { - String? buildName = validatedBuildNameForPlatform(PlatformType.ios, 'xyz', logger); + String? buildName = validatedBuildNameForPlatform(TargetPlatform.ios, 'xyz', logger); expect(buildName, isNull); - buildName = validatedBuildNameForPlatform(PlatformType.ios, '0.0.1', logger); + buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '0.0.1', logger); expect(buildName, '0.0.1'); - buildName = validatedBuildNameForPlatform(PlatformType.ios, '123.456.xyz', logger); + buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '123.456.xyz', logger); expect(logger.traceText, contains('Invalid build-name')); expect(buildName, '123.456.0'); - buildName = validatedBuildNameForPlatform(PlatformType.ios, '123.xyz', logger); + buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '123.xyz', logger); expect(buildName, '123.0.0'); }); testWithoutContext('versionName for Android', () async { - String? buildName = validatedBuildNameForPlatform(PlatformType.android, '123.abc+-', logger); + String? buildName = validatedBuildNameForPlatform( + TargetPlatform.android_arm, + '123.abc+-', + logger, + ); expect(buildName, '123.abc+-'); - buildName = validatedBuildNameForPlatform(PlatformType.android, 'abc+-', logger); + buildName = validatedBuildNameForPlatform(TargetPlatform.android_arm, 'abc+-', logger); expect(buildName, 'abc+-'); }); @@ -102,25 +106,11 @@ void main() { expect(CpuArch.x64.darwinArchName, 'x86_64'); }); - testWithoutContext('getName derives the canonical name from the platform and arch', () { - // iOS and macOS include the CPU architecture in their name (e.g. - // `ios-arm64`, `darwin-x64`), falling back to the bare platform name when - // the architecture is unknown. - expect(const TargetPlatform(.ios, .arm64).getName(), 'ios-arm64'); - expect(const TargetPlatform(.ios, .x64).getName(), 'ios-x64'); - expect(const TargetPlatform(.ios, .unknown).getName(), 'ios'); - expect(const TargetPlatform(.macos, .arm64).getName(), 'darwin-arm64'); - expect(const TargetPlatform(.macos, .x64).getName(), 'darwin-x64'); - expect(const TargetPlatform(.macos, .unknown).getName(), 'darwin'); - // Desktop platforms follow the `-` convention. - expect(const TargetPlatform(.linux, .x64).getName(), 'linux-x64'); - expect(const TargetPlatform(.linux, .arm64).getName(), 'linux-arm64'); - expect(const TargetPlatform(.windows, .arm64).getName(), 'windows-arm64'); - // Android has its own per-arch names. - expect(const TargetPlatform(.android, .armv7).getName(), 'android-arm'); - expect(const TargetPlatform(.android, .arm64).getName(), 'android-arm64'); - expect(const TargetPlatform(.android, .x64).getName(), 'android-x64'); - expect(const TargetPlatform(.android, .unknown).getName(), 'android'); + testWithoutContext('getNameForTargetPlatform on Darwin arches', () { + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.arm64), 'ios-arm64'); + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.armv7), 'ios-armv7'); + expect(TargetPlatform.ios.getName(cpuArch: CpuArch.x64), 'ios-x86_64'); + expect(TargetPlatform.android.getName(), isNot(contains('ios'))); }); testUsingContext( diff --git a/packages/flutter_tools/test/general.shard/build_system/source_test.dart b/packages/flutter_tools/test/general.shard/build_system/source_test.dart index 0b162ab1b3c81..653b04b291b71 100644 --- a/packages/flutter_tools/test/general.shard/build_system/source_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/source_test.dart @@ -111,7 +111,7 @@ void main() { globals.fs.file(path).createSync(recursive: true); const fizzSource = Source.artifact( Artifact.windowsDesktopPath, - platform: TargetPlatform(.windows, .x64), + platform: TargetPlatform.windows_x64, ); fizzSource.accept(visitor); @@ -277,7 +277,7 @@ void main() { const fizzSource = Source.artifact( Artifact.windowsDesktopPath, - platform: TargetPlatform(.windows, .x64), + platform: TargetPlatform.windows_x64, ); fizzSource.accept(visitor); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart index eb9190b943729..9ccb733787282 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart @@ -174,7 +174,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -187,7 +187,7 @@ void main() { environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); await androidAot.build(environment); @@ -210,7 +210,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -225,7 +225,7 @@ void main() { environment.buildDir.createSync(recursive: true); environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); await androidAot.build(environment); @@ -252,7 +252,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -269,7 +269,7 @@ void main() { environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - await const AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release).build(environment); + await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release).build(environment); }); testUsingContext( @@ -294,7 +294,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .arm64), + platform: TargetPlatform.android_arm64, mode: BuildMode.release, ), '--deterministic', @@ -311,10 +311,7 @@ void main() { environment.buildDir.childFile('app.dill').createSync(); environment.buildDir.childFile('native_assets.json').createSync(); - await const AndroidAot( - TargetPlatform(.android, .arm64), - BuildMode.release, - ).build(environment); + await const AndroidAot(TargetPlatform.android_arm64, BuildMode.release).build(environment); }, ); @@ -329,7 +326,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); // Create required files. environment.buildDir 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 fb23a80562f99..427cde4f21c0b 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 @@ -844,9 +844,9 @@ flutter: testUsingContext( platform, () async { - final targetPlatform = platform == 'android' - ? const TargetPlatform(.ios, .arm64) - : const TargetPlatform(.android, .unknown); + final TargetPlatform targetPlatform = platform == 'android' + ? TargetPlatform.ios + : TargetPlatform.android; final bool didInclude = await setupAndBuildPlatformAsset(platform, targetPlatform); expect( 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 9fef520525d6d..64f77221e483f 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 @@ -49,7 +49,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: const TargetPlatform(.android, .armv7).getName(), + kTargetPlatform: TargetPlatform.android_arm.getName(), }, inputs: {}, artifacts: artifacts, @@ -62,7 +62,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: const TargetPlatform(.ios, .arm64).getName(), + kTargetPlatform: TargetPlatform.ios.getName(), }, inputs: {}, artifacts: artifacts, @@ -88,7 +88,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ); processManager.addCommands([ @@ -130,7 +130,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ); processManager.addCommands([ @@ -175,7 +175,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ); processManager.addCommands([ @@ -220,7 +220,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ); processManager.addCommands([ @@ -266,7 +266,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ); processManager.addCommands([ @@ -314,7 +314,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.debug, ); processManager.addCommands([ @@ -362,7 +362,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); processManager.addCommands([ @@ -393,7 +393,7 @@ void main() { await const KernelSnapshot().build( androidEnvironment - ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() + ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kTrackWidgetCreation] = 'false', ); @@ -411,7 +411,7 @@ void main() { final String build = androidEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .unknown), + platform: TargetPlatform.android, mode: BuildMode.debug, ); processManager.addCommands([ @@ -453,7 +453,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.debug, ); fileSystem.directory('/ios/Runner.xcodeproj').createSync(recursive: true); @@ -487,7 +487,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = const TargetPlatform(.ios, .arm64).getName() + ..defines[kTargetPlatform] = TargetPlatform.ios.getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' ..defines[kXcodeConfiguration] = 'Debug-chocolate' @@ -511,7 +511,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); fileSystem.directory('/macos/Runner.xcodeproj').createSync(recursive: true); @@ -544,7 +544,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() + ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kFlavor] = 'strawberry' ..defines[kXcodeConfiguration] = 'Debug-chocolate' @@ -568,7 +568,7 @@ void main() { final String build = iosEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ); processManager.addCommands([ @@ -600,7 +600,7 @@ void main() { await const KernelSnapshot().build( iosEnvironment - ..defines[kTargetPlatform] = const TargetPlatform(.macos, .x64).getName() + ..defines[kTargetPlatform] = TargetPlatform.darwin.getName() ..defines[kBuildMode] = BuildMode.debug.cliName ..defines[kDartDefines] = base64Encode(utf8.encode('FLUTTER_APP_FLAVOR=vanilla')) ..defines[kFlavor] = 'strawberry' @@ -624,7 +624,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.debug.cliName, - kTargetPlatform: const TargetPlatform(.android, .armv7).getName(), + kTargetPlatform: TargetPlatform.android_arm.getName(), }, processManager: processManager, artifacts: artifacts, @@ -634,7 +634,7 @@ void main() { final String build = testEnvironment.buildDir.path; final String flutterPatchedSdkPath = artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.debug, ); processManager.addCommands([ @@ -678,7 +678,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ), '--deterministic', @@ -693,7 +693,7 @@ void main() { androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true); androidEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment); + await const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); @@ -706,7 +706,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ), '--deterministic', @@ -723,7 +723,7 @@ void main() { androidEnvironment.buildDir.childFile('app.dill').createSync(recursive: true); androidEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const AotElfRelease(TargetPlatform(.android, .armv7)).build(androidEnvironment); + await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); @@ -732,7 +732,7 @@ void main() { androidEnvironment.defines.remove(kBuildMode); expect( - const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment), + const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment), throwsA(isA()), ); }); @@ -741,7 +741,7 @@ void main() { androidEnvironment.defines.remove(kTargetPlatform); expect( - const AotElfProfile(TargetPlatform(.android, .armv7)).build(androidEnvironment), + const AotElfProfile(TargetPlatform.android_arm).build(androidEnvironment), throwsA(isA()), ); }); @@ -803,7 +803,7 @@ void main() { FakeCommand( command: [ // This path is not known by the cache due to the iOS gen_snapshot split. - 'Artifact.genSnapshotArm64.ios.profile', + 'Artifact.genSnapshotArm64.TargetPlatform.ios.profile', '--deterministic', '--write-v8-snapshot-profile-to=code_size_1/snapshot.arm64.json', '--trace-precompiler-to=code_size_1/trace.arm64.json', @@ -867,7 +867,7 @@ void main() { command: [ artifacts.getArtifactPath( Artifact.genSnapshot, - platform: const TargetPlatform(.android, .armv7), + platform: TargetPlatform.android_arm, mode: BuildMode.profile, ), '--deterministic', @@ -883,7 +883,7 @@ void main() { ), ]); - await const AotElfRelease(TargetPlatform(.android, .armv7)).build(androidEnvironment); + await const AotElfRelease(TargetPlatform.android_arm).build(androidEnvironment); expect(processManager, hasNoRemainingExpectations); }); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart index 83e2981ff6122..151319ce2b490 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart @@ -38,7 +38,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( @@ -72,7 +72,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( @@ -105,7 +105,7 @@ void main() { logger: logger, ); environment.buildDir.createSync(recursive: true); - const androidAot = AndroidAot(TargetPlatform(.android, .arm64), BuildMode.release); + const androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const androidAotBundle = AndroidAotBundle(androidAot); final androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( 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 cde0c70bef988..72aeb07d84ca6 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 @@ -128,7 +128,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); expect( @@ -160,7 +160,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); expect(logger.errorText, isEmpty); @@ -181,7 +181,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); expect(logger.errorText, isEmpty); @@ -202,7 +202,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); expect( @@ -230,7 +230,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); final stdinSink = CompleterIOSink(); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -280,7 +280,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); final stdinSink = CompleterIOSink(); @@ -312,7 +312,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); final stdinSink = CompleterIOSink(); @@ -330,8 +330,8 @@ void main() { }); for (final platform in [ - const TargetPlatform(.android, .armv7), - const TargetPlatform(.web, .unknown), + TargetPlatform.android_arm, + TargetPlatform.web_javascript, ]) { testWithoutContext('Non-constant instances $platform', () async { final Environment environment = createEnvironment({ @@ -382,7 +382,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .arm64), + targetPlatform: TargetPlatform.android_arm64, ); addConstFinderInvocation( @@ -425,7 +425,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); addConstFinderInvocation( @@ -469,7 +469,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); final stdinSink = CompleterIOSink(); @@ -501,7 +501,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); final stdinSink = CompleterIOSink(throwOnAdd: true); @@ -535,7 +535,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -568,7 +568,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); @@ -611,7 +611,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, stdout: emptyConstFinderResult); @@ -652,7 +652,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, exitCode: -1); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart index 3736b6f2c8b00..bd5dc8e56291f 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart @@ -862,7 +862,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFramework.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical', outputDir.path, ], ); @@ -875,7 +875,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFrameworkDsym.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical', outputDir.path, ], ); @@ -888,7 +888,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFrameworkDsym.ios.debug.EnvironmentType.physical', + 'Artifact.flutterFrameworkDsym.TargetPlatform.ios.debug.EnvironmentType.physical', outputDir.path, ], exitCode: 1, @@ -932,7 +932,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterFramework.ios.debug.EnvironmentType.simulator', + 'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.simulator', outputDir.path, ], onRun: (_) => binary.createSync(recursive: true), @@ -979,7 +979,7 @@ void main() { final Directory dSYM = fileSystem.directory( artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.debug, environmentType: EnvironmentType.physical, ), @@ -1337,7 +1337,7 @@ void main() { final Directory dSYM = fileSystem.directory( artifacts.getArtifactPath( Artifact.flutterFrameworkDsym, - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, mode: BuildMode.debug, environmentType: EnvironmentType.physical, ), diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart index 61d907d9d6f90..5f3dbf7dbca86 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/linux_test.dart @@ -38,7 +38,7 @@ void main() { ); testEnvironment.buildDir.createSync(recursive: true); - await const UnpackLinux(TargetPlatform(.linux, .x64)).build(testEnvironment); + await const UnpackLinux(TargetPlatform.linux_x64).build(testEnvironment); expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_gtk.so'), exists); expect(fileSystem.file('linux/flutter/ephemeral/unrelated-stuff'), isNot(exists)); @@ -46,12 +46,12 @@ void main() { // Check if the target files are copied correctly. final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, mode: BuildMode.debug, ); expect(fileSystem.file('linux/flutter/ephemeral/$headersPathForX64/foo.h'), exists); @@ -59,11 +59,11 @@ void main() { final String icuDataPathForX64 = artifacts.getArtifactPath( Artifact.icuData, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, ); final String icuDataPathForArm64 = artifacts.getArtifactPath( Artifact.icuData, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, ); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForX64'), exists); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForArm64'), isNot(exists)); @@ -89,7 +89,7 @@ void main() { ); testEnvironment.buildDir.createSync(recursive: true); - await const UnpackLinux(TargetPlatform(.linux, .arm64)).build(testEnvironment); + await const UnpackLinux(TargetPlatform.linux_arm64).build(testEnvironment); expect(fileSystem.file('linux/flutter/ephemeral/libflutter_linux_gtk.so'), exists); expect(fileSystem.file('linux/flutter/ephemeral/unrelated-stuff'), isNot(exists)); @@ -97,12 +97,12 @@ void main() { // Check if the target files are copied correctly. final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, mode: BuildMode.debug, ); expect(fileSystem.file('linux/flutter/ephemeral/$headersPathForX64/foo.h'), isNot(exists)); @@ -110,11 +110,11 @@ void main() { final String icuDataPathForX64 = artifacts.getArtifactPath( Artifact.icuData, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, ); final String icuDataPathForArm64 = artifacts.getArtifactPath( Artifact.icuData, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, ); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForX64'), isNot(exists)); expect(fileSystem.file('linux/flutter/ephemeral/$icuDataPathForArm64'), exists); @@ -147,7 +147,7 @@ void main() { testEnvironment.buildDir.childFile('app.dill').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); + await const DebugBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); final Directory output = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -203,7 +203,7 @@ flutter: flavorFileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); writePackageConfigFiles(directory: flavorFileSystem.currentDirectory, mainLibName: 'example'); - await const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(environment); + await const DebugBundleLinuxAssets(TargetPlatform.linux_x64).build(environment); final Uint8List assetManifestData = environment.outputDir .childDirectory('flutter_assets') @@ -225,11 +225,11 @@ flutter: testWithoutContext("DebugBundleLinuxAssets' name depends on target platforms", () async { expect( - const DebugBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, + const DebugBundleLinuxAssets(TargetPlatform.linux_x64).name, 'debug_bundle_linux-x64_assets', ); expect( - const DebugBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, + const DebugBundleLinuxAssets(TargetPlatform.linux_arm64).name, 'debug_bundle_linux-arm64_assets', ); }); @@ -252,10 +252,8 @@ flutter: testEnvironment.buildDir.childFile('app.so').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const LinuxAotBundle( - AotElfProfile(TargetPlatform(.linux, .x64)), - ).build(testEnvironment); - await const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); + await const LinuxAotBundle(AotElfProfile(TargetPlatform.linux_x64)).build(testEnvironment); + await const ProfileBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); final Directory libDir = testEnvironment.outputDir.childDirectory('lib'); final Directory assetsDir = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -272,11 +270,11 @@ flutter: testWithoutContext("ProfileBundleLinuxAssets' name depends on target platforms", () async { expect( - const ProfileBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, + const ProfileBundleLinuxAssets(TargetPlatform.linux_x64).name, 'profile_bundle_linux-x64_assets', ); expect( - const ProfileBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, + const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64).name, 'profile_bundle_linux-arm64_assets', ); }); @@ -299,10 +297,8 @@ flutter: testEnvironment.buildDir.childFile('app.so').createSync(); testEnvironment.buildDir.childFile('native_assets.json').createSync(); - await const LinuxAotBundle( - AotElfRelease(TargetPlatform(.linux, .x64)), - ).build(testEnvironment); - await const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)).build(testEnvironment); + await const LinuxAotBundle(AotElfRelease(TargetPlatform.linux_x64)).build(testEnvironment); + await const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64).build(testEnvironment); final Directory libDir = testEnvironment.outputDir.childDirectory('lib'); final Directory assetsDir = testEnvironment.outputDir.childDirectory('flutter_assets'); @@ -319,11 +315,11 @@ flutter: testWithoutContext("ReleaseBundleLinuxAssets' name depends on target platforms", () async { expect( - const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .x64)).name, + const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64).name, 'release_bundle_linux-x64_assets', ); expect( - const ReleaseBundleLinuxAssets(TargetPlatform(.linux, .arm64)).name, + const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64).name, 'release_bundle_linux-arm64_assets', ); }); @@ -332,12 +328,12 @@ flutter: void setUpCacheDirectory(FileSystem fileSystem, Artifacts artifacts) { final String desktopPathForX64 = artifacts.getArtifactPath( Artifact.linuxDesktopPath, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, mode: BuildMode.debug, ); final String desktopPathForArm64 = artifacts.getArtifactPath( Artifact.linuxDesktopPath, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, mode: BuildMode.debug, ); fileSystem.file('$desktopPathForX64/unrelated-stuff').createSync(recursive: true); @@ -347,29 +343,22 @@ void setUpCacheDirectory(FileSystem fileSystem, Artifacts artifacts) { final String headersPathForX64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .x64), + platform: TargetPlatform.linux_x64, mode: BuildMode.debug, ); final String headersPathForArm64 = artifacts.getArtifactPath( Artifact.linuxHeaders, - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, mode: BuildMode.debug, ); fileSystem.file('$headersPathForX64/foo.h').createSync(recursive: true); fileSystem.file('$headersPathForArm64/foo.h').createSync(recursive: true); fileSystem - .file( - artifacts.getArtifactPath( - Artifact.icuData, - platform: const TargetPlatform(.linux, .x64), - ), - ) + .file(artifacts.getArtifactPath(Artifact.icuData, platform: TargetPlatform.linux_x64)) .createSync(); fileSystem - .file( - artifacts.getArtifactPath(Artifact.icuData, platform: const TargetPlatform(.linux, .arm64)), - ) + .file(artifacts.getArtifactPath(Artifact.icuData, platform: TargetPlatform.linux_arm64)) .createSync(); fileSystem diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart index 95fe7b8428bc0..a580bb35f82e6 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/macos_test.dart @@ -101,7 +101,7 @@ void main() { .directory( artifacts.getArtifactPath( Artifact.flutterMacOSFrameworkDsym, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.release, ), ) @@ -125,7 +125,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterMacOSFrameworkDsym.darwin-x64.release', + 'Artifact.flutterMacOSFrameworkDsym.TargetPlatform.darwin.release', environment.outputDir.path, ], ); @@ -358,7 +358,7 @@ void main() { '--filter', '- .DS_Store/', '--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r', - 'Artifact.flutterMacOSFrameworkDsym.darwin-x64.release', + 'Artifact.flutterMacOSFrameworkDsym.TargetPlatform.darwin.release', environment.outputDir.path, ], exitCode: 1, @@ -421,7 +421,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), ) @@ -430,7 +430,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), ) @@ -482,7 +482,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.vmSnapshotData, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), ) @@ -491,7 +491,7 @@ void main() { .file( artifacts.getArtifactPath( Artifact.isolateSnapshotData, - platform: const TargetPlatform(.macos, .x64), + platform: TargetPlatform.darwin, mode: BuildMode.debug, ), ) @@ -813,7 +813,7 @@ void main() { processManager.addCommands([ FakeCommand( command: [ - 'Artifact.genSnapshotArm64.darwin-x64.release', + 'Artifact.genSnapshotArm64.TargetPlatform.darwin.release', '--deterministic', '--snapshot_kind=app-aot-macho-dylib', '--macho=${environment.buildDir.childFile('arm64/App.framework/App').path}', @@ -826,7 +826,7 @@ void main() { ), FakeCommand( command: [ - 'Artifact.genSnapshotX64.darwin-x64.release', + 'Artifact.genSnapshotX64.TargetPlatform.darwin.release', '--deterministic', '--snapshot_kind=app-aot-macho-dylib', '--macho=${environment.buildDir.childFile('x86_64/App.framework/App').path}', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart index 7187c93bdf618..94dda581a3f96 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/shader_compiler_test.dart @@ -74,7 +74,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ), true, ); @@ -114,7 +114,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, ), true, ); @@ -155,7 +155,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.android, .unknown), + targetPlatform: TargetPlatform.android, ), true, ); @@ -194,7 +194,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(notFragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ), true, ); @@ -233,7 +233,7 @@ void main() { await shaderCompiler.compileShader( input: fileSystem.file(notFragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: TargetPlatform.web_javascript, ); fail('unreachable'); } on ShaderCompilerException catch (e) { @@ -289,7 +289,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -340,7 +340,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.tester, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.tester); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -391,7 +391,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -442,7 +442,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.tester, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.tester); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -493,7 +493,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -542,7 +542,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.web, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.web_javascript); final DevFSContent? content = await developmentShaderCompiler.recompileShader( DevFSFileContent(fileSystem.file(fragPath)), @@ -601,7 +601,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -659,7 +659,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -715,7 +715,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final shaderContent = DevFSFileContent(fileSystem.file(fragPath)); @@ -770,7 +770,7 @@ void main() { random: math.Random(0), ); - developmentShaderCompiler.configureCompiler(const TargetPlatform(.android, .unknown)); + developmentShaderCompiler.configureCompiler(TargetPlatform.android); final shaderContent = DevFSByteContent(Uint8List.fromList([1, 2, 3, 4])); @@ -820,7 +820,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, ), throwsToolExit(message: 'Impeller shader compiler was blocked by security policy.'), ); @@ -867,7 +867,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, ), throwsToolExit(message: 'Impeller shader compiler was blocked by security policy.'), ); @@ -913,7 +913,7 @@ void main() { final bool success = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, fatal: false, ); @@ -952,7 +952,7 @@ void main() { shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, ), throwsA( isA().having( @@ -1012,7 +1012,7 @@ void main() { final bool success1 = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, fatal: false, ); expect(success1, false); @@ -1026,7 +1026,7 @@ void main() { final bool success2 = await shaderCompiler.compileShader( input: fileSystem.file(fragPath), outputPath: outputPath, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, fatal: false, ); expect(success2, false); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart index d24e6049f7fb1..10f17402ba58f 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_dry_run_test.dart @@ -83,7 +83,7 @@ name: my_app fakeFlutterVersion: FakeFlutterVersion(), ); commandArgs = [ - 'Artifact.engineDartBinary.web-javascript', + 'Artifact.engineDartBinary.TargetPlatform.web_javascript', 'compile', 'wasm', '--packages=/.dart_tool/package_config.json', 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 c648e331a2374..a51af6e45bfd6 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 @@ -28,7 +28,7 @@ import '../../../src/testbed.dart'; import '../../../src/throwing_pub.dart'; const _kDart2jsLinuxArgs = [ - 'Artifact.engineDartBinary.web-javascript', + 'Artifact.engineDartBinary.TargetPlatform.web_javascript', 'compile', 'js', '--platform-binaries=HostArtifact.webPlatformKernelFolder', @@ -44,7 +44,7 @@ const _kStandardFlutterWebDefines = [ ]; const _kDart2WasmLinuxArgs = [ - 'Artifact.engineDartBinary.web-javascript', + 'Artifact.engineDartBinary.TargetPlatform.web_javascript', 'compile', 'wasm', '--packages=/.dart_tool/package_config.json', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart index a2345146ae412..4a41adc5b15e1 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/windows_test.dart @@ -38,17 +38,17 @@ void main() { final String windowsDesktopPath = artifacts.getArtifactPath( Artifact.windowsDesktopPath, - platform: const TargetPlatform(.windows, .x64), + platform: TargetPlatform.windows_x64, mode: BuildMode.debug, ); final String windowsCppClientWrapper = artifacts.getArtifactPath( Artifact.windowsCppClientWrapper, - platform: const TargetPlatform(.windows, .x64), + platform: TargetPlatform.windows_x64, mode: BuildMode.debug, ); final String icuData = artifacts.getArtifactPath( Artifact.icuData, - platform: const TargetPlatform(.windows, .x64), + platform: TargetPlatform.windows_x64, ); final requiredFiles = [ '$windowsDesktopPath\\flutter_export.h', @@ -70,7 +70,7 @@ void main() { } fileSystem.directory('windows').createSync(); - await const UnpackWindows(TargetPlatform(.windows, .x64)).build(environment); + await const UnpackWindows(TargetPlatform.windows_x64).build(environment); // Output files are copied correctly. expect(fileSystem.file(r'C:\windows\flutter\ephemeral\flutter_export.h'), exists); @@ -165,7 +165,7 @@ void main() { environment.buildDir.childFile('app.dill').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); + await const DebugBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); // Depfile is created and dill is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); @@ -214,7 +214,7 @@ flutter: flavorFileSystem.file('assets/strawberry/ice-cream.png').createSync(recursive: true); writePackageConfigFiles(directory: flavorFileSystem.currentDirectory, mainLibName: 'example'); - await const DebugBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); + await const DebugBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); final Uint8List assetManifestData = environment.outputDir .childDirectory('flutter_assets') @@ -249,10 +249,8 @@ flutter: environment.buildDir.childFile('app.so').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const WindowsAotBundle( - AotElfProfile(TargetPlatform(.windows, .x64)), - ).build(environment); - await const ProfileBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); + await const WindowsAotBundle(AotElfProfile(TargetPlatform.windows_x64)).build(environment); + await const ProfileBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); // Depfile is created and so is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); @@ -280,10 +278,8 @@ flutter: environment.buildDir.childFile('app.so').createSync(recursive: true); environment.buildDir.childFile('native_assets.json').createSync(recursive: true); - await const WindowsAotBundle( - AotElfRelease(TargetPlatform(.windows, .x64)), - ).build(environment); - await const ReleaseBundleWindowsAssets(TargetPlatform(.windows, .x64)).build(environment); + await const WindowsAotBundle(AotElfRelease(TargetPlatform.windows_x64)).build(environment); + await const ReleaseBundleWindowsAssets(TargetPlatform.windows_x64).build(environment); // Depfile is created and so is copied. expect(environment.buildDir.childFile('flutter_assets.d'), exists); 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 d824a849b32d3..c7e0cddf97131 100644 --- a/packages/flutter_tools/test/general.shard/bundle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/bundle_builder_test.dart @@ -44,7 +44,7 @@ void main() { }); await BundleBuilder().build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), @@ -119,7 +119,7 @@ void main() { await writeBundle( bundleDir, bundle.entries, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, impellerStatus: ImpellerStatus.platformDefault, processManager: processManager, fileSystem: fileSystem, @@ -140,7 +140,7 @@ void main() { () { expect( () => BundleBuilder().build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: 'lib/main.dart', @@ -177,7 +177,7 @@ void main() { }); await BundleBuilder().build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildInfo: const BuildInfo( BuildMode.debug, null, @@ -200,7 +200,7 @@ void main() { expect(env, isNotNull); expect(env!.defines[kBuildMode], 'debug'); - expect(env!.defines[kTargetPlatform], 'ios-arm64'); + expect(env!.defines[kTargetPlatform], 'ios'); expect(env!.defines[kTargetFile], mainPath); expect(env!.defines[kTrackWidgetCreation], 'true'); expect(env!.defines[kFrontendServerStarterPath], 'path/to/frontend_server_starter.dart'); @@ -312,7 +312,7 @@ void main() { } }); await BundleBuilder().build( - platform: const TargetPlatform(.ios, .arm64), + platform: TargetPlatform.ios, buildInfo: BuildInfo.release, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), diff --git a/packages/flutter_tools/test/general.shard/cold_test.dart b/packages/flutter_tools/test/general.shard/cold_test.dart index 566a4153b8ed5..b64e4c1a55e69 100644 --- a/packages/flutter_tools/test/general.shard/cold_test.dart +++ b/packages/flutter_tools/test/general.shard/cold_test.dart @@ -209,7 +209,7 @@ class FakeDevice extends Fake implements Device { String get displayName => name; @override - Future get targetPlatform async => const TargetPlatform(.tester, .unknown); + Future get targetPlatform async => TargetPlatform.tester; @override DartDevelopmentService get dds => FakeDartDevelopmentService(); @@ -259,7 +259,7 @@ class TestFlutterDevice extends FlutterDevice { required this.exception, required ResidentCompiler generator, }) : super( - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, device, buildInfo: BuildInfo.debug, generator: generator, diff --git a/packages/flutter_tools/test/general.shard/compile_expression_test.dart b/packages/flutter_tools/test/general.shard/compile_expression_test.dart index b2827c898acd7..2cfece7bd6466 100644 --- a/packages/flutter_tools/test/general.shard/compile_expression_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_expression_test.dart @@ -36,7 +36,7 @@ void main() { fileSystem = MemoryFileSystem.test() ..file(Artifact.flutterPatchedSdkPath.toString()).createSync(); generator = const ResidentCompilerFactory().create( - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: .tester, buildInfo: BuildInfo.debug, artifacts: Artifacts.test(fileSystem: fileSystem), processManager: processManager, diff --git a/packages/flutter_tools/test/general.shard/compile_test.dart b/packages/flutter_tools/test/general.shard/compile_test.dart index 00dc4df72901f..0be122136050a 100644 --- a/packages/flutter_tools/test/general.shard/compile_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_test.dart @@ -163,7 +163,7 @@ void main() { // Initializing the compiler with includeUnsupportedPlatformLibraryStubs for targets other // than DDC is not currently supported as it's limited for use with the widget previewer. for (final TargetPlatform target in TargetPlatform.values.where( - (e) => e != const TargetPlatform(.web, .unknown), + (e) => e != .web_javascript, )) { try { const ResidentCompilerFactory().create( @@ -190,7 +190,7 @@ void main() { // Initializing the compiler with includeUnsupportedPlatformLibraryStubs for DDC is // supported. const ResidentCompilerFactory().create( - targetPlatform: const TargetPlatform(.web, .unknown), + targetPlatform: .web_javascript, buildInfo: BuildInfo.debug.copyWith(includeUnsupportedPlatformLibraryStubs: true), logger: BufferLogger.test(), processManager: FakeProcessManager.any(), @@ -210,8 +210,8 @@ void main() { final processManager = FakeProcessManager.list([ FakeCommand( command: const [ - 'Artifact.engineDartAotRuntime.web-javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', + 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', '--sdk-root', 'sdkroot/', '--incremental', @@ -285,8 +285,8 @@ void main() { final processManager = FakeProcessManager.list([ FakeCommand( command: const [ - 'Artifact.engineDartAotRuntime.web-javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', + 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', '--sdk-root', 'sdkroot/', '--incremental', diff --git a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart index c86b705494b6b..51790a14af8e8 100644 --- a/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart +++ b/packages/flutter_tools/test/general.shard/custom_devices/custom_device_test.dart @@ -118,7 +118,7 @@ void main() { expect(device.name, 'testlabel'); expect(device.platformType, PlatformType.custom); expect(await device.sdkNameAndVersion, 'testsdknameandversion'); - expect(await device.targetPlatform, const TargetPlatform(.linux, .arm64)); + expect(await device.targetPlatform, TargetPlatform.linux_arm64); expect(await device.installApp(linuxApp), true); expect(await device.uninstallApp(linuxApp), true); expect(await device.isLatestBuildInstalled(linuxApp), false); @@ -537,7 +537,7 @@ void main() { final runDebugCompleter = Completer(); final CustomDeviceConfig config = testConfig.copyWith( - platform: const TargetPlatform(.linux, .arm64), + platform: TargetPlatform.linux_arm64, postBuildCommand: const [ 'testpostbuild', r'--buildMode=${buildMode}', @@ -675,12 +675,12 @@ void main() { testWithoutContext('CustomDevice returns correct target platform', () async { final device = CustomDevice( - config: testConfig.copyWith(platform: const TargetPlatform(.linux, .x64)), + config: testConfig.copyWith(platform: TargetPlatform.linux_x64), logger: BufferLogger.test(), processManager: FakeProcessManager.empty(), ); - expect(await device.targetPlatform, const TargetPlatform(.linux, .x64)); + expect(await device.targetPlatform, TargetPlatform.linux_x64); }); testWithoutContext( diff --git a/packages/flutter_tools/test/general.shard/darwin_test.dart b/packages/flutter_tools/test/general.shard/darwin_test.dart index 738658e22e687..c2cc60c9087d9 100644 --- a/packages/flutter_tools/test/general.shard/darwin_test.dart +++ b/packages/flutter_tools/test/general.shard/darwin_test.dart @@ -28,13 +28,10 @@ void main() { }); testWithoutContext('fromTargetPlatform', () { expect( - FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.ios, .arm64)), + FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.ios), FlutterDarwinPlatform.ios, ); - expect( - FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.android, .unknown)), - null, - ); + expect(FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.android), null); }); testWithoutContext('fromName', () { expect(FlutterDarwinPlatform.fromName('ios'), FlutterDarwinPlatform.ios); @@ -58,13 +55,10 @@ void main() { }); testWithoutContext('fromTargetPlatform', () { expect( - FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.macos, .x64)), + FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.darwin), FlutterDarwinPlatform.macos, ); - expect( - FlutterDarwinPlatform.fromTargetPlatform(const TargetPlatform(.android, .unknown)), - null, - ); + expect(FlutterDarwinPlatform.fromTargetPlatform(TargetPlatform.android), null); }); testWithoutContext('fromName', () { expect(FlutterDarwinPlatform.fromName('macos'), FlutterDarwinPlatform.macos); diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart index a7b9d298ce777..382c94131383d 100644 --- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart +++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart @@ -467,7 +467,7 @@ class FakeDesktopDevice extends DesktopDevice { String get name => 'dummy'; @override - Future get targetPlatform async => const TargetPlatform(.tester, .unknown); + Future get targetPlatform async => TargetPlatform.tester; @override Future get cpuArch async => CpuArch.unknown; @@ -517,7 +517,7 @@ class FakeMacOSDevice extends MacOSDevice { String get name => 'dummy'; @override - Future get targetPlatform async => const TargetPlatform(.tester, .unknown); + Future get targetPlatform async => TargetPlatform.tester; @override Future isSupported() async => true; diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index ced11f011e57d..a99a17db621ad 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -288,9 +288,9 @@ void main() { isSupportedForProject: false, ); final webDevice = FakeDevice('webby', 'webby') - ..targetPlatform = Future.value(const TargetPlatform(.web, .unknown)); + ..targetPlatform = Future.value(TargetPlatform.web_javascript); final fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay') - ..targetPlatform = Future.value(const TargetPlatform(.fuchsia, .x64)); + ..targetPlatform = Future.value(TargetPlatform.fuchsia_x64); final unconnectedDevice = FakeDevice('ephemeralTwo', 'ephemeralTwo', isConnected: false); final wirelessDevice = FakeDevice( 'ephemeralTwo', diff --git a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart index 14fd7786b689f..594773bebdf9f 100644 --- a/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/drive_service_test.dart @@ -535,7 +535,7 @@ class FakeDevice extends Fake implements Device { final DartDevelopmentService dds = FakeDartDevelopmentService(); @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; @override Future getLogReader({ diff --git a/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart index cc47482154ad8..60a6a7f980a31 100644 --- a/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart +++ b/packages/flutter_tools/test/general.shard/drive/web_driver_service_test.dart @@ -451,5 +451,5 @@ class FakeDevice extends Fake implements Device { final PlatformType platformType = PlatformType.web; @override - Future get targetPlatform async => const TargetPlatform(.android, .armv7); + Future get targetPlatform async => TargetPlatform.android_arm; } diff --git a/packages/flutter_tools/test/general.shard/emulator_test.dart b/packages/flutter_tools/test/general.shard/emulator_test.dart index b3076fa6c862e..5d56a74ff27ef 100644 --- a/packages/flutter_tools/test/general.shard/emulator_test.dart +++ b/packages/flutter_tools/test/general.shard/emulator_test.dart @@ -8,7 +8,6 @@ import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/emulator.dart'; import 'package:flutter_tools/src/ios/ios_emulators.dart'; diff --git a/packages/flutter_tools/test/general.shard/flutter_platform_test.dart b/packages/flutter_tools/test/general.shard/flutter_platform_test.dart index 8f06e8af1b367..5a3e16f017690 100644 --- a/packages/flutter_tools/test/general.shard/flutter_platform_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_platform_test.dart @@ -539,8 +539,7 @@ class _UnstartableDevice extends Fake implements Device { Future dispose() => Future.value(); @override - Future get targetPlatform => - Future.value(const TargetPlatform(.android, .unknown)); + Future get targetPlatform => Future.value(TargetPlatform.android); @override Future stopApp(ApplicationPackage? app, {String? userIdentifier}) async { @@ -569,8 +568,7 @@ class _WorkingDevice extends Fake implements Device { Future dispose() async {} @override - Future get targetPlatform => - Future.value(const TargetPlatform(.android, .unknown)); + Future get targetPlatform => Future.value(TargetPlatform.android); @override Future stopApp(ApplicationPackage? app, {String? userIdentifier}) async => true; diff --git a/packages/flutter_tools/test/general.shard/hot_shared.dart b/packages/flutter_tools/test/general.shard/hot_shared.dart index 2eb84d4bc8375..76a84984fbec7 100644 --- a/packages/flutter_tools/test/general.shard/hot_shared.dart +++ b/packages/flutter_tools/test/general.shard/hot_shared.dart @@ -42,7 +42,7 @@ class FakeDevFs extends Fake implements DevFS { } class FakeDevice extends Fake implements Device { - FakeDevice({TargetPlatform targetPlatform = const TargetPlatform(.tester, .unknown)}) + FakeDevice({TargetPlatform targetPlatform = TargetPlatform.tester}) : _targetPlatform = targetPlatform; final TargetPlatform _targetPlatform; @@ -148,7 +148,7 @@ class TestFlutterDevice extends FlutterDevice { required ResidentCompiler generator, }) : super( device, - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: TargetPlatform.unsupported, buildInfo: BuildInfo.debug, generator: generator, developmentShaderCompiler: const FakeShaderCompiler(), diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index 92c9a79acd2a2..fb1c6735b71ee 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -250,7 +250,7 @@ name: my_app final devices = [ FlutterDevice( device, - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), @@ -281,7 +281,7 @@ name: my_app final devices = [ FlutterDevice( device, - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, generator: residentCompiler, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), diff --git a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart index 730bc758a85cb..aedfe1ece2777 100644 --- a/packages/flutter_tools/test/general.shard/integration_test_device_test.dart +++ b/packages/flutter_tools/test/general.shard/integration_test_device_test.dart @@ -123,7 +123,7 @@ void main() { isA().having( (Exception e) => e.toString(), 'description', - contains('No application found for android-arm'), + contains('No application found for TargetPlatform.android_arm'), ), ), ); diff --git a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart index 9f954385aa5ff..879276cddb958 100644 --- a/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/android/native_assets_test.dart @@ -93,7 +93,7 @@ void main() { }; final DartHooksResult result = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.android, .arm64), + targetPlatform: TargetPlatform.android_arm64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, @@ -104,7 +104,7 @@ void main() { await installCodeAssets( dartHookResult: result, environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.android, .arm64), + targetPlatform: TargetPlatform.android_arm64, projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -142,7 +142,7 @@ void main() { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, }, - targetPlatform: const TargetPlatform(.android, .x64), + targetPlatform: TargetPlatform.android_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(), @@ -170,7 +170,7 @@ void main() { kBuildMode: BuildMode.debug.cliName, kMinSdkVersion: minSdkVersion, }, - targetPlatform: const TargetPlatform(.android, .arm64), + targetPlatform: TargetPlatform.android_arm64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutNdk(packagesWithNativeAssetsResult: ['bar']), diff --git a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart index bce7fc681e919..e305e9a7749c7 100644 --- a/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/build_system/targets/native_assets_test.dart @@ -39,7 +39,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: const TargetPlatform(.ios, .arm64).getName(), + kTargetPlatform: TargetPlatform.ios.getName(), kIosArchs: 'arm64', kSdkRoot: 'path/to/iPhoneOS.sdk', }, @@ -53,7 +53,7 @@ void main() { fileSystem.currentDirectory, defines: { kBuildMode: BuildMode.profile.cliName, - kTargetPlatform: const TargetPlatform(.android, .unknown).getName(), + kTargetPlatform: TargetPlatform.android.getName(), kAndroidArchs: CpuArch.arm64.androidPlatformName, }, inputs: {}, diff --git a/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart index b3b3ebeecc57e..1467271824cbf 100644 --- a/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/data_assets_test.dart @@ -58,7 +58,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: const TargetPlatform(.windows, .x64), + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -100,7 +100,7 @@ void main() { expect( () async => runFlutterSpecificHooks( environmentDefines: {kBuildMode: buildMode.cliName}, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, buildCodeAssets: const BuildCodeAssetsOptions(appBuildDirectory: null), buildDataAssets: true, diff --git a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart index 169eb2c7df4f1..4152781956729 100644 --- a/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/ios/native_assets_test.dart @@ -257,7 +257,7 @@ void main() { }; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, projectUri: projectUri, fileSystem: fileSystem, buildRunner: buildRunner, @@ -268,7 +268,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.ios, .arm64), + targetPlatform: TargetPlatform.ios, projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, diff --git a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart index d83d1d309946f..3cfb0a8de6364 100644 --- a/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/linux/native_assets_test.dart @@ -59,7 +59,7 @@ void main() { await runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: _BuildRunnerWithoutClang(), @@ -296,8 +296,10 @@ CMAKE_LINKER:FILEPATH=/some/path/to/ld.lld 'cCompilerConfigLinux FileSystemException on resolveSymbolicLinks and throwIfNotFound: false', overrides: { ProcessManager: () => FakeProcessManager.empty(), - FileSystem: () => - _ThrowingResolveFileSystem(fileSystem, '${environment.outputDir.path}/mock_clang++'), + FileSystem: () => _ThrowingResolveFileSystem( + fileSystem, + '${environment.outputDir.path}/mock_clang++', + ), }, () async { if (!const LocalPlatform().isLinux) { @@ -325,8 +327,10 @@ CMAKE_LINKER:FILEPATH=/some/path/to/ld.lld 'cCompilerConfigLinux FileSystemException on resolveSymbolicLinks and throwIfNotFound: true', overrides: { ProcessManager: () => FakeProcessManager.empty(), - FileSystem: () => - _ThrowingResolveFileSystem(fileSystem, '${environment.outputDir.path}/mock_clang++'), + FileSystem: () => _ThrowingResolveFileSystem( + fileSystem, + '${environment.outputDir.path}/mock_clang++', + ), }, () async { if (!const LocalPlatform().isLinux) { diff --git a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart index 001b5786b2722..13a6c109b5301 100644 --- a/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/macos/native_assets_test.dart @@ -284,7 +284,7 @@ void main() { } if (flutterTester && !const LocalPlatform().isMacOS) { // The [runFlutterSpecificDartBuild] will - when given - // `TargetPlatform(.tester, .unknown)` - enable `flutter test` mode. That means if + // `TargetPlatform.tester` - enable `flutter test` mode. That means if // this test is run on linux, it's going to do a linux build. // Though this test is mac-specific, so we skip that. // @@ -333,9 +333,9 @@ void main() { kBuildMode: buildMode.cliName, kDarwinArchs: 'arm64 x86_64', }; - final targetPlatform = flutterTester - ? const TargetPlatform(.tester, .unknown) - : const TargetPlatform(.macos, .x64); + final TargetPlatform targetPlatform = flutterTester + ? TargetPlatform.tester + : TargetPlatform.darwin; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, diff --git a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart index 3011dcff04625..6926e7ece5988 100644 --- a/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/native_assets_test.dart @@ -72,7 +72,7 @@ void main() { ]; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -87,7 +87,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.windows, .x64), + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -111,7 +111,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: const TargetPlatform(.windows, .x64), + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -140,7 +140,7 @@ void main() { final environmentDefines = {kBuildMode: BuildMode.debug.cliName}; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.windows, .x64), + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -154,7 +154,7 @@ void main() { await installCodeAssets( dartHookResult: dartHookResult, environmentDefines: environmentDefines, - targetPlatform: const TargetPlatform(.windows, .x64), + targetPlatform: TargetPlatform.windows_x64, projectUri: projectUri, fileSystem: fileSystem, nativeAssetsFileUri: nonFlutterTesterAssetUri, @@ -178,7 +178,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.debug.cliName}, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -217,7 +217,7 @@ void main() { // Release mode means the dart build has linking enabled. kBuildMode: BuildMode.release.cliName, }, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -267,7 +267,7 @@ void main() { expect( () => runFlutterSpecificHooks( environmentDefines: {kBuildMode: BuildMode.release.cliName}, - targetPlatform: const TargetPlatform(.linux, .x64), + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: FakeFlutterNativeAssetsBuildRunner( @@ -338,7 +338,7 @@ void main() { await runFlutterSpecificHooks( environmentDefines: {}, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, projectUri: projectUri, fileSystem: fileSystem, buildRunner: target, @@ -381,7 +381,7 @@ CMAKE_LINKER:FILEPATH=/usr/bin/ld.ldd await runFlutterSpecificHooks( environmentDefines: {kBuildMode: 'release'}, - targetPlatform: const TargetPlatform(.linux, .arm64), + targetPlatform: TargetPlatform.linux_arm64, projectUri: projectUri, fileSystem: fileSystem, buildRunner: target, diff --git a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart index d027aebbb5544..e00f52cb9cbd2 100644 --- a/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/resident_runner_test.dart @@ -36,17 +36,14 @@ void main() { testUsingContext( 'use the nativeAssetsYamlFile when provided', () => testbed.run(() async { - final device = FakeDevice( - targetPlatform: const TargetPlatform(.macos, .x64), - sdkNameAndVersion: 'Macos', - ); + final device = FakeDevice(targetPlatform: TargetPlatform.darwin, sdkNameAndVersion: 'Macos'); final residentCompiler = FakeResidentCompiler(); final flutterDevice = FakeFlutterDevice() ..testUri = testUri ..vmServiceHost = (() => fakeVmServiceHost) ..device = device ..fakeDevFS = devFS - ..targetPlatform = const TargetPlatform(.macos, .x64) + ..targetPlatform = TargetPlatform.darwin ..generator = residentCompiler; fakeVmServiceHost = FakeVmServiceHost(requests: [listViews, listViews]); diff --git a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart index aca9461f817cf..0ee3f47fa669a 100644 --- a/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/isolated/windows/native_assets_test.dart @@ -99,9 +99,9 @@ void main() { : FakeFlutterNativeAssetsBuilderResult.fromAssets(codeAssets: codeAssets), ); final environmentDefines = {kBuildMode: buildMode.cliName}; - final targetPlatform = flutterTester - ? const TargetPlatform(.tester, .unknown) - : const TargetPlatform(.windows, .x64); + final TargetPlatform targetPlatform = flutterTester + ? TargetPlatform.tester + : TargetPlatform.windows_x64; final DartHooksResult dartHookResult = await runFlutterSpecificHooks( environmentDefines: environmentDefines, targetPlatform: targetPlatform, diff --git a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart index 8afcf5004568f..34bbb38f745f3 100644 --- a/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart +++ b/packages/flutter_tools/test/general.shard/linux/linux_device_test.dart @@ -31,7 +31,7 @@ void main() { ); final linuxApp = PrebuiltLinuxApp(executable: 'foo'); - expect(await device.targetPlatform, const TargetPlatform(.linux, .x64)); + expect(await device.targetPlatform, TargetPlatform.linux_x64); expect(device.name, 'Linux'); expect(await device.installApp(linuxApp), true); expect(await device.uninstallApp(linuxApp), true); @@ -53,7 +53,7 @@ void main() { fileSystem: MemoryFileSystem.test(), operatingSystemUtils: FakeOperatingSystemUtils(hostPlatform: HostPlatform.linux_arm64), ); - expect(await deviceArm64Host.targetPlatform, const TargetPlatform(.linux, .arm64)); + expect(await deviceArm64Host.targetPlatform, TargetPlatform.linux_arm64); }); testWithoutContext('LinuxDevice: no devices listed if platform unsupported', () async { diff --git a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart index f814d49f5993c..428cc4f4698e1 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_device_test.dart @@ -35,7 +35,7 @@ void main() { ); final package = FakeMacOSApp(); - expect(await device.targetPlatform, const TargetPlatform(.macos, .x64)); + expect(await device.targetPlatform, TargetPlatform.darwin); expect(device.name, 'macOS'); expect(await device.installApp(package), true); expect(await device.uninstallApp(package), true); diff --git a/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart b/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart index 7d3843a29debf..55266b38be9c1 100644 --- a/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/macos_ipad_device_test.dart @@ -122,7 +122,7 @@ void main() { expect(await device.isLocalEmulator, isFalse); expect(device.name, 'Mac Designed for iPad'); expect(device.portForwarder, isNot(isNull)); - expect(await device.targetPlatform, const TargetPlatform(.macos, .arm64)); + expect(await device.targetPlatform, TargetPlatform.darwin); expect(await device.installApp(FakeApplicationPackage()), isTrue); expect(await device.isAppInstalled(FakeApplicationPackage()), isTrue); diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart index 6ab59fd1d9b57..fb55e6722298e 100644 --- a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart @@ -1282,7 +1282,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { final String name; @override - Future get targetPlatform async => const TargetPlatform(.ios, .arm64); + Future get targetPlatform async => TargetPlatform.ios; @override Future isSupported() async => true; diff --git a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart index d9cfae7c62099..21aa7c7090044 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_helpers.dart @@ -177,7 +177,7 @@ class TestFlutterDevice extends FlutterDevice { : _vmServiceUris = vmServiceUris, super( generator: FakeResidentCompiler(), - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, buildInfo: BuildInfo.debug, developmentShaderCompiler: const FakeShaderCompiler(), ); @@ -220,7 +220,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler(); @override - TargetPlatform targetPlatform = const TargetPlatform(.android, .unknown); + TargetPlatform targetPlatform = TargetPlatform.android; @override Stream get vmServiceUris => Stream.value(testUri); @@ -310,7 +310,7 @@ class FakeDelegateFlutterDevice extends FlutterDevice { ResidentCompiler residentCompiler, this.fakeDevFS, ) : super( - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, buildInfo: buildInfo, generator: residentCompiler, developmentShaderCompiler: const FakeShaderCompiler(), @@ -389,7 +389,7 @@ class FakeProjectFileInvalidator extends Fake implements ProjectFileInvalidator class FakeDevice extends Fake implements Device { FakeDevice({ String sdkNameAndVersion = 'Android', - TargetPlatform targetPlatform = const TargetPlatform(.android, .armv7), + TargetPlatform targetPlatform = TargetPlatform.android_arm, bool isLocalEmulator = false, this.supportsHotRestart = true, this.supportsScreenshot = true, @@ -416,9 +416,8 @@ class FakeDevice extends Fake implements Device { bool supportsFlutterExit; @override - PlatformType get platformType => _targetPlatform == const TargetPlatform(.web, .unknown) - ? PlatformType.web - : PlatformType.android; + PlatformType get platformType => + _targetPlatform == TargetPlatform.web_javascript ? PlatformType.web : PlatformType.android; @override Future get sdkNameAndVersion async => _sdkNameAndVersion; diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index 17458c71fea74..1564f472becb1 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -240,7 +240,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: const TargetPlatform(.android, .armv7).getName(), + targetPlatform: TargetPlatform.android_arm.getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -308,7 +308,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'reload-barred', - targetPlatform: const TargetPlatform(.android, .armv7).getName(), + targetPlatform: TargetPlatform.android_arm.getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -361,7 +361,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: const TargetPlatform(.android, .armv7).getName(), + targetPlatform: TargetPlatform.android_arm.getName(), sdkName: 'Android', emulator: false, fullRestart: false, @@ -579,7 +579,7 @@ void main() { final Event event = fakeAnalytics.sentEvents.first; expect(event.eventName.label, 'hot_runner_info'); expect(event.eventData['label'], 'reload'); - expect(event.eventData['targetPlatform'], const TargetPlatform(.android, .armv7).getName()); + expect(event.eventData['targetPlatform'], TargetPlatform.android_arm.getName()); }), ); @@ -725,10 +725,7 @@ void main() { expect(hotRunnerInfoEvents, hasLength(1)); final Event newEvent = hotRunnerInfoEvents.first; expect(newEvent.eventData['label'], 'restart'); - expect( - newEvent.eventData['targetPlatform'], - const TargetPlatform(.android, .armv7).getName(), - ); + expect(newEvent.eventData['targetPlatform'], TargetPlatform.android_arm.getName()); }), ); @@ -946,7 +943,7 @@ void main() { contains( Event.hotRunnerInfo( label: 'exception', - targetPlatform: const TargetPlatform(.android, .armv7).getName(), + targetPlatform: TargetPlatform.android_arm.getName(), sdkName: 'Android', emulator: false, fullRestart: true, @@ -1313,7 +1310,7 @@ flutter: 'ResidentRunner printHelpDetails hides v on web in profile mode', () => testbed.run(() async { final FlutterDevice flutterDevice = await FlutterDevice.create( - FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)), + FakeDevice(targetPlatform: TargetPlatform.web_javascript), target: 'lib/main.dart', buildInfo: BuildInfo.profile, platform: FakePlatform(), @@ -1688,7 +1685,7 @@ flutter: 'FlutterDevice uses dartdevc configuration when targeting web', () async { fakeVmServiceHost = FakeVmServiceHost(requests: []); - final device = FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)); + final device = FakeDevice(targetPlatform: TargetPlatform.web_javascript); final residentCompiler = (await FlutterDevice.create( device, @@ -1739,7 +1736,7 @@ flutter: 'FlutterDevice uses dartdevc configuration when targeting web with null-safety autodetected', () async { fakeVmServiceHost = FakeVmServiceHost(requests: []); - final device = FakeDevice(targetPlatform: const TargetPlatform(.web, .unknown)); + final device = FakeDevice(targetPlatform: TargetPlatform.web_javascript); final residentCompiler = (await FlutterDevice.create( @@ -2124,7 +2121,7 @@ flutter: final webFlutterDevice = FakeFlutterDevice() ..vmServiceHost = (() => fakeVmServiceHost) ..fakeDevFS = devFS - ..targetPlatform = const TargetPlatform(.web, .unknown); + ..targetPlatform = TargetPlatform.web_javascript; fakeVmServiceHost = FakeVmServiceHost( requests: [ listViews, @@ -2237,17 +2234,14 @@ flutter: testUsingContext( 'use the nativeAssetsYamlFile when provided', () => testbed.run(() async { - final device = FakeDevice( - targetPlatform: const TargetPlatform(.macos, .x64), - sdkNameAndVersion: 'Macos', - ); + final device = FakeDevice(targetPlatform: TargetPlatform.darwin, sdkNameAndVersion: 'Macos'); final residentCompiler = FakeResidentCompiler(); final flutterDevice = FakeFlutterDevice() ..testUri = testUri ..vmServiceHost = (() => fakeVmServiceHost) ..device = device ..fakeDevFS = devFS - ..targetPlatform = const TargetPlatform(.macos, .x64) + ..targetPlatform = TargetPlatform.darwin ..generator = residentCompiler; fakeVmServiceHost = FakeVmServiceHost(requests: [listViews, listViews]); @@ -2335,7 +2329,7 @@ flutter: testUsingContext( 'correctly caches Web Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = const TargetPlatform(.web, .unknown); + flutterDevice.targetPlatform = TargetPlatform.web_javascript; residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( @@ -2355,7 +2349,7 @@ flutter: testUsingContext( 'correctly caches Fuchsia Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = const TargetPlatform(.fuchsia, .arm64); + flutterDevice.targetPlatform = TargetPlatform.fuchsia_arm64; residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( @@ -2375,7 +2369,7 @@ flutter: testUsingContext( 'correctly caches Android Device compilation', () => testbed.run(() { - flutterDevice.targetPlatform = const TargetPlatform(.android, .armv7); + flutterDevice.targetPlatform = TargetPlatform.android_arm; residentRunner.testCacheInitialDillCompilation(); final String expectedPath = getDefaultCachedKernelPath( diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index 9843e5d69b73b..754492f7ae180 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -2508,7 +2508,7 @@ class FakeFlutterDevice extends Fake implements FlutterDevice { Exception? reportError; @override - TargetPlatform get targetPlatform => const TargetPlatform(.web, .unknown); + TargetPlatform get targetPlatform => TargetPlatform.web_javascript; @override ResidentCompiler? generator; diff --git a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart index 399774e9decd9..0d2919b3e51de 100644 --- a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart @@ -3110,7 +3110,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.attached, this.platformType = PlatformType.android, - TargetPlatform deviceTargetPlatform = const TargetPlatform(.android, .unknown), + TargetPlatform deviceTargetPlatform = TargetPlatform.android, }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3126,7 +3126,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.wireless, this.platformType = PlatformType.android, - TargetPlatform deviceTargetPlatform = const TargetPlatform(.android, .unknown), + TargetPlatform deviceTargetPlatform = TargetPlatform.android, }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3142,7 +3142,7 @@ class FakeDevice extends Fake implements Device { this.isConnected = true, this.connectionInterface = DeviceConnectionInterface.attached, this.platformType = PlatformType.fuchsia, - TargetPlatform deviceTargetPlatform = const TargetPlatform(.fuchsia, .arm64), + TargetPlatform deviceTargetPlatform = TargetPlatform.fuchsia_arm64, }) : id = deviceId ?? 'xxx', name = deviceName ?? 'test', _isSupported = deviceSupported, @@ -3204,7 +3204,7 @@ class FakeDevice extends Fake implements Device { Category? get category => Category.mobile; @override - Future get targetPlatformDisplayName async => (await targetPlatform).devicePlatformName; + Future get targetPlatformDisplayName async => (await targetPlatform).getName(); } class FakeIOSDevice extends Fake implements IOSDevice { @@ -3314,7 +3314,7 @@ class FakeIOSDevice extends Fake implements IOSDevice { Future get targetPlatformDisplayName async => 'ios'; @override - Future get targetPlatform async => const TargetPlatform(.tester, .unknown); + Future get targetPlatform async => TargetPlatform.tester; } class FakeTerminal extends Fake implements AnsiTerminal { diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart index dd5af7dcbd3d7..addc8780ed7fb 100644 --- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart @@ -1293,7 +1293,7 @@ void main() { final residentRunner = FakeResidentRunner( FlutterDevice( FakeDevice(), - targetPlatform: const TargetPlatform(.unsupported, .unknown), + targetPlatform: .unsupported, buildInfo: BuildInfo.debug, generator: FakeResidentCompiler(), developmentShaderCompiler: const FakeShaderCompiler(), @@ -1674,9 +1674,7 @@ TerminalHandler setUpTerminalHandler( ), generator: FakeResidentCompiler(), developmentShaderCompiler: const FakeShaderCompiler(), - targetPlatform: web - ? const TargetPlatform(.web, .unknown) - : const TargetPlatform(.android, .armv7), + targetPlatform: web ? TargetPlatform.web_javascript : TargetPlatform.android_arm, ); device.vmService = nullVmService ? null : FakeVmServiceHost(requests: requests).vmService; final residentRunner = FakeResidentRunner(device, testLogger, localFileSystem) diff --git a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart index b8574203056a9..5b23d99259417 100644 --- a/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart +++ b/packages/flutter_tools/test/general.shard/test/web_test_compiler_test.dart @@ -37,8 +37,8 @@ void main() { final processManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'Artifact.engineDartAotRuntime.web-javascript', - 'Artifact.frontendServerSnapshotForEngineDartSdk.web-javascript', + 'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript', + 'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript', '--sdk-root', 'HostArtifact.flutterWebSdk/', '--incremental', @@ -118,7 +118,7 @@ void main() { final processManager = FakeProcessManager.list([ const FakeCommand( command: [ - 'Artifact.engineDartBinary.web-javascript', + 'Artifact.engineDartBinary.TargetPlatform.web_javascript', 'compile', 'wasm', '--packages=.dart_tool/package_config.json', diff --git a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart index ec32edfd65e3f..b03042970f904 100644 --- a/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart +++ b/packages/flutter_tools/test/general.shard/tester/flutter_tester_test.dart @@ -111,7 +111,7 @@ void main() { expect(await device.isLocalEmulator, isFalse); expect(device.name, 'Flutter test device'); expect(device.portForwarder, isNot(isNull)); - expect(await device.targetPlatform, const TargetPlatform(.tester, .unknown)); + expect(await device.targetPlatform, TargetPlatform.tester); expect(await device.installApp(FakeApplicationPackage()), isTrue); expect(await device.isAppInstalled(FakeApplicationPackage()), isFalse); diff --git a/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart b/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart index 38c8556baf2c6..dd740d4a997a6 100644 --- a/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/build_windows_flavor_test.dart @@ -72,7 +72,7 @@ void main() { FakeCommand cmakeGenerationCommand({ String? flavor, - TargetPlatform targetPlatform = const TargetPlatform(.windows, .x64), + TargetPlatform targetPlatform = TargetPlatform.windows_x64, }) { final String buildDir = flavor != null && flavor.isNotEmpty ? r'C:\build\windows\x64\' + flavor @@ -93,7 +93,10 @@ void main() { ); } - FakeCommand buildCommand(String buildMode, {String? flavor}) { + FakeCommand buildCommand( + String buildMode, { + String? flavor, + }) { final String buildDir = flavor != null && flavor.isNotEmpty ? r'C:\build\windows\x64\' + flavor : r'C:\build\windows\x64'; @@ -134,7 +137,7 @@ void main() { 'returns legacy path when no flavor', () { expect( - getWindowsBuildDirectory(const TargetPlatform(.windows, .x64)), + getWindowsBuildDirectory(TargetPlatform.windows_x64), endsWith(fileSystem.path.join('windows', 'x64')), ); }, @@ -148,7 +151,7 @@ void main() { 'inserts flavor segment when flavor is set', () { expect( - getWindowsBuildDirectory(const TargetPlatform(.windows, .x64), 'apple'), + getWindowsBuildDirectory(TargetPlatform.windows_x64, 'apple'), endsWith(fileSystem.path.join('windows', 'x64', 'apple')), ); }, diff --git a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart index e089d1bd9f6c6..45558a038c23c 100644 --- a/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/windows_device_test.dart @@ -24,7 +24,7 @@ void main() { final File dummyFile = MemoryFileSystem.test().file('dummy'); final windowsApp = PrebuiltWindowsApp(executable: 'foo', applicationPackage: dummyFile); - expect(await windowsDevice.targetPlatform, const TargetPlatform(.windows, .x64)); + expect(await windowsDevice.targetPlatform, TargetPlatform.windows_x64); expect(windowsDevice.name, 'Windows'); expect(await windowsDevice.installApp(windowsApp), true); expect(await windowsDevice.uninstallApp(windowsApp), true); diff --git a/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart b/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart index 2d8960fb31c67..8028f93f857b5 100644 --- a/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart +++ b/packages/flutter_tools/test/integration.shard/shader_compiler_test.dart @@ -32,9 +32,9 @@ void main() { outputPath: tmpDir.childFile('test_shader.frag.out').path, targetPlatform: targetSkslOnly // web_javascript compiles to sksl only. - ? const TargetPlatform(.web, .unknown) + ? TargetPlatform.web_javascript // tester compiles to sksl and runtime-stage-vulkan - : const TargetPlatform(.tester, .unknown), + : TargetPlatform.tester, ); } @@ -66,7 +66,7 @@ void main() { final bool compileResult = await shaderCompiler.compileShader( input: globals.fs.file(inkSparklePath), outputPath: inkSparkleOutputPath, - targetPlatform: const TargetPlatform(.tester, .unknown), + targetPlatform: TargetPlatform.tester, ); final File resultFile = globals.fs.file(inkSparkleOutputPath); diff --git a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart index aeaed75728386..13cf6728c1a1b 100644 --- a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart +++ b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart @@ -372,7 +372,7 @@ Future _analyzeEntity(FileSystemEntity target) async { final ProcessResult exec = await Process.run( globals.artifacts!.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), args, workingDirectory: target is Directory ? target.path : target.dirname, @@ -400,7 +400,7 @@ Future _runFlutterSnapshot(List flutterCommandArgs, Directory work final args = [ globals.artifacts!.getArtifactPath( Artifact.engineDartBinary, - platform: const TargetPlatform(.web, .unknown), + platform: TargetPlatform.web_javascript, ), flutterToolsSnapshotPath, ...flutterCommandArgs, diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index 8a264f8914097..4745aa6b32fb5 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -37,7 +37,7 @@ List fakeDevices = [ ), FakeDeviceJsonData( FakeDevice('webby', 'webby') - ..targetPlatform = Future.value(const TargetPlatform(.web, .unknown)) + ..targetPlatform = Future.value(TargetPlatform.web_javascript) ..cpuArch = Future.value(CpuArch.unknown) ..sdkNameAndVersion = Future.value('Web SDK (1.2.4)'), { @@ -90,7 +90,7 @@ List fakeDevices = [ type: PlatformType.ios, connectionInterface: DeviceConnectionInterface.wireless, ) - ..targetPlatform = Future.value(const TargetPlatform(.ios, .arm64)) + ..targetPlatform = Future.value(TargetPlatform.ios) ..cpuArch = Future.value(CpuArch.arm64) ..sdkNameAndVersion = Future.value('iOS 16'), { @@ -167,9 +167,7 @@ class FakeDevice extends Device { Future dispose() async {} @override - Future targetPlatform = Future.value( - const TargetPlatform(.android, .armv7), - ); + Future targetPlatform = Future.value(TargetPlatform.android_arm); @override Future cpuArch = Future.value(CpuArch.armv7); From 07248566708bbd98447f948b61731e6cc275c5fd Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 02:32:06 -0400 Subject: [PATCH 113/147] Roll Skia from 78afc18c9ba0 to b8bda91f02db (3 revisions) (#190268) https://skia.googlesource.com/skia.git/+log/78afc18c9ba0..b8bda91f02db 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from 3824c8346c38 to 8f7ec5882f46 2026-07-30 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-30 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 3be25c7653fce..1e03a0045a096 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '78afc18c9ba01a2a6c13d530992241b3a6f82205', + 'skia_revision': 'b8bda91f02dbaa7870a3649bd5130babbb24a5d3', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From aa068aa1c1e8b511259b8ddf9021bc7727981243 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 04:35:11 -0400 Subject: [PATCH 114/147] Roll Fuchsia Linux SDK from J8rVTlBnjnUpuHYk3... to _J8wM3kyQpLN9wvRD... (#190273) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC aaclarke@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1e03a0045a096..6b6d9d273d4bf 100644 --- a/DEPS +++ b/DEPS @@ -833,7 +833,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'J8rVTlBnjnUpuHYk3GUV56K4jK0m7uFmr1Q0qKsZnrgC' + 'version': '_J8wM3kyQpLN9wvRD5upBr9L1g5TCYF3Oc8P0m3QeZMC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From 7b5791253e609405e642ef1ce8dd1d2dff536ab9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 04:39:32 -0400 Subject: [PATCH 115/147] Roll Skia from b8bda91f02db to 177de1940bf2 (5 revisions) (#190272) https://skia.googlesource.com/skia.git/+log/b8bda91f02db..177de1940bf2 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from c09abf1d3a9f to 22ba45a57477 (13 revisions) 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 952256d4e39e to 54cbd6cba08d (5 revisions) 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 1ec13b321130 to 7f1c4bcf56a3 (8 revisions) 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 92c4ae4cfeb2 to e832cc409215 (15 revisions) 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll bazel_build from 70733f74d415bf389... to d3af3547496f60d9c... (1 revision) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 6b6d9d273d4bf..988b50fa0d093 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'b8bda91f02dbaa7870a3649bd5130babbb24a5d3', + 'skia_revision': '177de1940bf2bad20d9be58a3013c2b2d7aa9bf2', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From aad7ccb11964b431eba4aec3fd7f348c94cd3e28 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 08:56:11 -0400 Subject: [PATCH 116/147] Roll Skia from 177de1940bf2 to 491301f393d1 (1 revision) (#190279) https://skia.googlesource.com/skia.git/+log/177de1940bf2..491301f393d1 2026-07-30 robertphillips@google.com Defer DrawPass Pipeline resolution to insertRecording time If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 988b50fa0d093..f346407bcda37 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '177de1940bf2bad20d9be58a3013c2b2d7aa9bf2', + 'skia_revision': '491301f393d120cd1ffcfe659b67375a80c70e81', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From d0da4767346ae787dc60168c8581e3e57ecda6b5 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 11:34:07 -0400 Subject: [PATCH 117/147] Roll Packages from dd11626c81a3 to 7d17fc869daf (1 revision) (#190293) https://github.com/flutter/packages/compare/dd11626c81a3...7d17fc869daf 2026-07-29 43054281+camsim99@users.noreply.github.com [tool] Update `githooks/README` with Git hooks uninstallation instructions (flutter/packages#12311) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 0432b4c82ea5b..8be15b87661b0 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -dd11626c81a3972e1718dd3e9750195ebccbaa3d +7d17fc869daf572994367a750c626817a506d667 From c21841d4a27ce03ee4364da94e22b446dee9dc7d Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 12:50:12 -0400 Subject: [PATCH 118/147] Roll Skia from 491301f393d1 to 50cf67910462 (3 revisions) (#190294) https://skia.googlesource.com/skia.git/+log/491301f393d1..50cf67910462 2026-07-30 borenet@google.com Revert "[infra] Cleanup after removing Upload tasks" 2026-07-30 borenet@google.com [infra] Cleanup after removing Upload tasks 2026-07-30 lehoangquyen@chromium.org graphite/dawn: support true Discard load/store ops If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f346407bcda37..416c8f3f65743 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '491301f393d120cd1ffcfe659b67375a80c70e81', + 'skia_revision': '50cf67910462acc1e5b6474d0ec9d5f35e44c54c', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From aed492925745e874d8e180fe23035c0270d5e3b7 Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Thu, 30 Jul 2026 09:53:21 -0700 Subject: [PATCH 119/147] ci: mac-must-wait part duex (#190295) Test still racy: "ios/artifacts.zip" required by flutter tool in the test, but not actually verified. --- .github/workflows/mac-verify-binaries.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac-verify-binaries.yml b/.github/workflows/mac-verify-binaries.yml index fafb784227276..0effc07e308c8 100644 --- a/.github/workflows/mac-verify-binaries.yml +++ b/.github/workflows/mac-verify-binaries.yml @@ -46,7 +46,7 @@ jobs: uses: ./.github/actions/wait-for-engine-build with: github-token: ${{ secrets.GITHUB_TOKEN }} - check-name: 'Mac mac_host_engine,Linux linux_host_engine' + check-name: 'Mac mac_host_engine,Mac mac_ios_engine,Linux linux_host_engine' - uses: ./.github/actions/composite-flutter-setup From 7027945822cd5d1d511d31029bf5ebf14fcb2bd7 Mon Sep 17 00:00:00 2001 From: Bernardo Ferrari Date: Thu, 30 Jul 2026 14:04:25 -0300 Subject: [PATCH 120/147] Add `ShapeBorder` hit test fast paths (#187962) Sequence to https://github.com/flutter/flutter/pull/108648 (4 year delay). This adds a `bool hitTest` that makes code much faster by not using `path`. 4 years ago, the speed-up was 17%. Interaction happens less often than painting, but still seems like it is worth patching it up. image ChatGPT Image Jun 13, 2026, 02_25_20
AM --------- Co-authored-by: Tong Mu --- .../flutter/lib/src/painting/borders.dart | 23 +++ .../flutter/lib/src/painting/box_border.dart | 5 + .../lib/src/painting/circle_border.dart | 9 ++ .../painting/rounded_rectangle_border.dart | 39 +++++ .../lib/src/painting/shape_decoration.dart | 2 +- .../lib/src/painting/stadium_border.dart | 20 +++ .../test/painting/shape_decoration_test.dart | 143 ++++++++++++++++++ 7 files changed, 240 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/painting/borders.dart b/packages/flutter/lib/src/painting/borders.dart index 1d6a33a58eb30..a228ee491c7d5 100644 --- a/packages/flutter/lib/src/painting/borders.dart +++ b/packages/flutter/lib/src/painting/borders.dart @@ -562,6 +562,24 @@ abstract class ShapeBorder { /// * [Path.contains], which can tell if an [Offset] is within a [Path]. Path getInnerPath(Rect rect, {TextDirection? textDirection}); + /// Tests whether the outer boundary of this border contains [position]. + /// + /// The [position] must be in the same coordinate space as [rect]. The default + /// implementation checks [position] against the path returned by + /// [getOuterPath]. + /// + /// The `textDirection` argument must be provided and non-null if the border + /// has a text direction dependency. It may be null if the border will not need + /// the text direction to describe its geometry. + /// + /// See also: + /// + /// * [getOuterPath], which creates the path for the outer edge. + /// * [ShapeDecoration.hitTest], which delegates to this method. + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + return getOuterPath(rect, textDirection: textDirection).contains(position); + } + /// Paint a canvas with the appropriate shape. /// /// On [ShapeBorder] subclasses whose [preferPaintInterior] method returns @@ -825,6 +843,11 @@ class _CompoundBorder extends ShapeBorder { return borders.first.getOuterPath(rect, textDirection: textDirection); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + return borders.first.hitTest(rect, position, textDirection: textDirection); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { borders.first.paintInterior(canvas, rect, paint, textDirection: textDirection); diff --git a/packages/flutter/lib/src/painting/box_border.dart b/packages/flutter/lib/src/painting/box_border.dart index 425e6d745be55..2ae1e905767dd 100644 --- a/packages/flutter/lib/src/painting/box_border.dart +++ b/packages/flutter/lib/src/painting/box_border.dart @@ -227,6 +227,11 @@ abstract class BoxBorder extends ShapeBorder { return Path()..addRect(rect); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + return rect.contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { // For `ShapeDecoration(shape: Border.all())`, a rectangle with sharp edges diff --git a/packages/flutter/lib/src/painting/circle_border.dart b/packages/flutter/lib/src/painting/circle_border.dart index 2f02344953669..e5c5d55b8c638 100644 --- a/packages/flutter/lib/src/painting/circle_border.dart +++ b/packages/flutter/lib/src/painting/circle_border.dart @@ -87,6 +87,15 @@ class CircleBorder extends OutlinedBorder { return Path()..addOval(_adjustRect(rect)); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + final Rect adjustedRect = _adjustRect(rect); + return RRect.fromRectAndRadius( + adjustedRect, + Radius.elliptical(adjustedRect.width / 2.0, adjustedRect.height / 2.0), + ).contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { if (eccentricity == 0.0) { diff --git a/packages/flutter/lib/src/painting/rounded_rectangle_border.dart b/packages/flutter/lib/src/painting/rounded_rectangle_border.dart index 7cdcd77b91e8b..045f84b2fdb2d 100644 --- a/packages/flutter/lib/src/painting/rounded_rectangle_border.dart +++ b/packages/flutter/lib/src/painting/rounded_rectangle_border.dart @@ -108,6 +108,15 @@ class RoundedRectangleBorder extends OutlinedBorder with _RRectLikeBorder { return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + final BorderRadius resolvedBorderRadius = borderRadius.resolve(textDirection); + if (resolvedBorderRadius == BorderRadius.zero) { + return rect.contains(position); + } + return resolvedBorderRadius.toRRect(rect).contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { if (borderRadius == BorderRadius.zero) { @@ -183,6 +192,11 @@ class _RoundedRectangleToCircleBorder extends _ShapeToCircleBorder extends Outlined void drawShape(Canvas canvas, Rect rect, BorderRadius radius, Paint paint, [double? inflation]); Path buildPath(Rect rect, BorderRadius radius, [double? inflation]); + bool containsOuterShape(Rect rect, BorderRadius radius, Offset position); final BorderRadiusGeometry borderRadius; final double circularity; @@ -544,6 +573,16 @@ abstract class _ShapeToCircleBorder extends Outlined return buildPath(_adjustRect(rect), _adjustBorderRadius(rect, textDirection)); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + final Rect adjustedRect = _adjustRect(rect); + final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect, textDirection); + if (adjustedBorderRadius == BorderRadius.zero) { + return adjustedRect.contains(position); + } + return containsOuterShape(adjustedRect, adjustedBorderRadius, position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect, textDirection); diff --git a/packages/flutter/lib/src/painting/shape_decoration.dart b/packages/flutter/lib/src/painting/shape_decoration.dart index 5f0a66c051344..ec8b9a51e4bf6 100644 --- a/packages/flutter/lib/src/painting/shape_decoration.dart +++ b/packages/flutter/lib/src/painting/shape_decoration.dart @@ -294,7 +294,7 @@ class ShapeDecoration extends Decoration { @override bool hitTest(Size size, Offset position, {TextDirection? textDirection}) { - return shape.getOuterPath(Offset.zero & size, textDirection: textDirection).contains(position); + return shape.hitTest(Offset.zero & size, position, textDirection: textDirection); } @override diff --git a/packages/flutter/lib/src/painting/stadium_border.dart b/packages/flutter/lib/src/painting/stadium_border.dart index 1821c2d6f8e9b..daba29c75a13a 100644 --- a/packages/flutter/lib/src/painting/stadium_border.dart +++ b/packages/flutter/lib/src/painting/stadium_border.dart @@ -96,6 +96,12 @@ class StadiumBorder extends OutlinedBorder { return Path()..addRRect(RRect.fromRectAndRadius(rect, radius)); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + final radius = Radius.circular(rect.shortestSide / 2.0); + return RRect.fromRectAndRadius(rect, radius).contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { final radius = Radius.circular(rect.shortestSide / 2.0); @@ -248,6 +254,11 @@ class _StadiumToCircleBorder extends OutlinedBorder { return Path()..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect))); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + return _adjustBorderRadius(rect).toRRect(_adjustRect(rect)).contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { canvas.drawRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)), paint); @@ -391,6 +402,15 @@ class _StadiumToRoundedRectangleBorder extends OutlinedBorder { return Path()..addRRect(_adjustBorderRadius(rect).resolve(textDirection).toRRect(rect)); } + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect).resolve(textDirection); + if (adjustedBorderRadius == BorderRadius.zero) { + return rect.contains(position); + } + return adjustedBorderRadius.toRRect(rect).contains(position); + } + @override void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { final BorderRadiusGeometry adjustedBorderRadius = _adjustBorderRadius(rect); diff --git a/packages/flutter/test/painting/shape_decoration_test.dart b/packages/flutter/test/painting/shape_decoration_test.dart index 62575abef04f4..b446cc1b9e52e 100644 --- a/packages/flutter/test/painting/shape_decoration_test.dart +++ b/packages/flutter/test/painting/shape_decoration_test.dart @@ -99,6 +99,92 @@ void main() { expect(b.hitTest(size, const Offset(20.0, 50.0)), isTrue); }); + test('ShapeBorder.hitTest defaults to getOuterPath', () { + _outerPathCount = 0; + const ShapeBorder border = _PathHitTestBorder(); + const rect = Rect.fromLTWH(10.0, 20.0, 100.0, 50.0); + + expect(border.hitTest(rect, const Offset(20.0, 30.0)), isTrue); + expect(border.hitTest(rect, Offset.zero), isFalse); + expect(_outerPathCount, 2); + }); + + test('ShapeDecoration.hitTest delegates to ShapeBorder.hitTest', () { + _hitTestCount = 0; + const decoration = ShapeDecoration(shape: _HitTestBorder()); + + expect(decoration.hitTest(const Size(100.0, 100.0), const Offset(50.0, 50.0)), isFalse); + expect(_hitTestCount, 1); + }); + + test('ShapeBorder.hitTest matches getOuterPath for primitive shapes', () { + const rect = Rect.fromLTWH(0.0, 0.0, 120.0, 80.0); + const TextDirection textDirection = TextDirection.ltr; + const positions = [ + Offset(-10.0, 40.0), // Outside every shape. + Offset(1.0, 1.0), // Distinguishes square and rounded corners. + Offset(0.5, 14.5), // Distinguishes RRect and superellipse corners. + Offset(1.0, 35.0), // Exercises the left edge of wide shapes. + Offset(1.0, 68.0), // Exercises the directional bottom-left corner. + Offset(11.0, 35.0), // Exercises CircleBorder eccentricity. + Offset(14.5, 13.5), // Distinguishes interpolated outer shapes. + Offset(21.0, 35.0), // Inside every shape. + ]; + final borders = [ + Border.all(), + const CircleBorder(), + const CircleBorder(eccentricity: 0.5), + const OvalBorder(), + const RoundedRectangleBorder(), + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(18.0))), + const RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.only(topStart: Radius.circular(18.0)), + ), + const RoundedSuperellipseBorder(), + const RoundedSuperellipseBorder(borderRadius: BorderRadius.all(Radius.circular(18.0))), + const StadiumBorder(), + ShapeBorder.lerp( + const CircleBorder(), + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(18.0))), + 0.5, + )!, + ShapeBorder.lerp( + const CircleBorder(), + const RoundedSuperellipseBorder(borderRadius: BorderRadius.all(Radius.circular(18.0))), + 0.5, + )!, + ShapeBorder.lerp(const StadiumBorder(), const CircleBorder(), 0.5)!, + ShapeBorder.lerp( + const StadiumBorder(), + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(18.0))), + 0.5, + )!, + ]; + + // Every position is tested against every border, so these collections are + // intentionally independent rather than paired test cases. + for (final border in borders) { + for (final position in positions) { + expect( + border.hitTest(rect, position, textDirection: textDirection), + border.getOuterPath(rect, textDirection: textDirection).contains(position), + reason: '$border at $position', + ); + } + } + }); + + test('_CompoundBorder.hitTest preserves child hitTest optimizations', () { + _hitTestCount = 0; + final ShapeBorder compoundBorder = const RoundedRectangleBorder() + const _HitTestBorder(); + + expect( + compoundBorder.hitTest(const Rect.fromLTWH(0.0, 0.0, 100.0, 100.0), Offset.zero), + isFalse, + ); + expect(_hitTestCount, 1); + }); + test('ShapeDecoration.lerp between gradient and color is smooth and does not throw', () { // Regression test for https://github.com/flutter/flutter/issues/93953 const colorR = Color(0xffff0000); @@ -224,3 +310,60 @@ class TestImageProvider extends ImageProvider { return OneFrameImageStreamCompleter(SynchronousFuture(ImageInfo(image: image))); } } + +int _outerPathCount = 0; + +class _PathHitTestBorder extends ShapeBorder { + const _PathHitTestBorder(); + + @override + EdgeInsetsGeometry get dimensions => EdgeInsets.zero; + + @override + ShapeBorder scale(double t) => this; + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { + throw StateError('ShapeBorder.hitTest should not call getInnerPath.'); + } + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { + _outerPathCount += 1; + return Path()..addRect(rect); + } + + @override + void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {} +} + +int _hitTestCount = 0; + +class _HitTestBorder extends ShapeBorder { + const _HitTestBorder(); + + @override + EdgeInsetsGeometry get dimensions => EdgeInsets.zero; + + @override + ShapeBorder scale(double t) => this; + + @override + Path getInnerPath(Rect rect, {TextDirection? textDirection}) { + throw StateError('ShapeDecoration.hitTest should not call getInnerPath.'); + } + + @override + Path getOuterPath(Rect rect, {TextDirection? textDirection}) { + throw StateError('hitTest should not call getOuterPath directly.'); + } + + @override + bool hitTest(Rect rect, Offset position, {TextDirection? textDirection}) { + _hitTestCount += 1; + return false; + } + + @override + void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {} +} From 4a6a70a0dcfd3e453c8d34caf60ad05dfd982e7e Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Thu, 30 Jul 2026 12:17:50 -0700 Subject: [PATCH 121/147] Migrate layers example to material_ui (#190237) Migrates the layers example to material_ui. Maybe in the future we want to move this example to material_ui, or refactor it to not use material_ui, but this PR is not considering that for now. I did a `flutter run` for each of the affected examples here, and they all ran fine. Part of https://github.com/flutter/flutter/issues/190093. --- dev/bots/check_examples_cross_imports.dart | 8 -------- examples/layers/pubspec.yaml | 5 ++--- examples/layers/rendering/touch_input.dart | 2 +- examples/layers/services/isolate.dart | 2 +- examples/layers/test/gestures_test.dart | 2 +- examples/layers/widgets/gestures.dart | 2 +- examples/layers/widgets/media_query.dart | 2 +- examples/layers/widgets/sectors.dart | 2 +- examples/layers/widgets/spinning_mixed.dart | 2 +- examples/layers/widgets/styled_text.dart | 2 +- pubspec.lock | 16 ++++++++++++++++ 11 files changed, 26 insertions(+), 19 deletions(-) diff --git a/dev/bots/check_examples_cross_imports.dart b/dev/bots/check_examples_cross_imports.dart index 699c150102f9e..8887a3c7fb77f 100644 --- a/dev/bots/check_examples_cross_imports.dart +++ b/dev/bots/check_examples_cross_imports.dart @@ -570,14 +570,6 @@ class ExamplesCrossImportChecker { 'examples/api/test/widgets/text_magnifier/text_magnifier.0_test.dart', 'examples/flutter_view/lib/main.dart', 'examples/image_list/lib/main.dart', - 'examples/layers/rendering/touch_input.dart', - 'examples/layers/services/isolate.dart', - 'examples/layers/widgets/gestures.dart', - 'examples/layers/widgets/spinning_mixed.dart', - 'examples/layers/widgets/media_query.dart', - 'examples/layers/widgets/sectors.dart', - 'examples/layers/widgets/styled_text.dart', - 'examples/layers/test/gestures_test.dart', 'examples/multiple_windows/lib/app/main_window.dart', 'examples/multiple_windows/lib/app/tooltip_button.dart', 'examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart', diff --git a/examples/layers/pubspec.yaml b/examples/layers/pubspec.yaml index 204a6440ff4eb..c3529ed0aee52 100644 --- a/examples/layers/pubspec.yaml +++ b/examples/layers/pubspec.yaml @@ -8,16 +8,15 @@ resolution: workspace dependencies: flutter: sdk: flutter - + material_ui: ^0.0.2 dev_dependencies: flutter_test: sdk: flutter - flutter: assets: - services/data.json uses-material-design: true -# PUBSPEC CHECKSUM: 60tfp7 +# PUBSPEC CHECKSUM: oescuq diff --git a/examples/layers/rendering/touch_input.dart b/examples/layers/rendering/touch_input.dart index dcb46b4ee22c7..2d7030bbe13b9 100644 --- a/examples/layers/rendering/touch_input.dart +++ b/examples/layers/rendering/touch_input.dart @@ -5,8 +5,8 @@ // This example shows how to use process input events in the underlying render // tree. -import 'package:flutter/material.dart'; // Imported just for its color palette. import 'package:flutter/rendering.dart'; +import 'package:material_ui/material_ui.dart'; // Imported just for its color palette. import 'src/binding.dart'; diff --git a/examples/layers/services/isolate.dart b/examples/layers/services/isolate.dart index 228be44c1a1ee..12d9a632769a4 100644 --- a/examples/layers/services/isolate.dart +++ b/examples/layers/services/isolate.dart @@ -5,8 +5,8 @@ import 'dart:convert'; import 'dart:isolate'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:material_ui/material_ui.dart'; typedef OnProgressListener = void Function(double completed, double total); typedef OnResultListener = void Function(String result); diff --git a/examples/layers/test/gestures_test.dart b/examples/layers/test/gestures_test.dart index 47cd843b06bcd..044c8b103b0fc 100644 --- a/examples/layers/test/gestures_test.dart +++ b/examples/layers/test/gestures_test.dart @@ -2,8 +2,8 @@ // 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_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart'; import '../widgets/gestures.dart'; diff --git a/examples/layers/widgets/gestures.dart b/examples/layers/widgets/gestures.dart index ae622f31901ae..e45b39e9c45e2 100644 --- a/examples/layers/widgets/gestures.dart +++ b/examples/layers/widgets/gestures.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 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; class _GesturePainter extends CustomPainter { const _GesturePainter({ diff --git a/examples/layers/widgets/media_query.dart b/examples/layers/widgets/media_query.dart index 281170966e542..4c67e9e6b11ca 100644 --- a/examples/layers/widgets/media_query.dart +++ b/examples/layers/widgets/media_query.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 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; class AdaptedListItem extends StatelessWidget { const AdaptedListItem({super.key, required this.name}); diff --git a/examples/layers/widgets/sectors.dart b/examples/layers/widgets/sectors.dart index 3a48868d33d21..9871ca9a73faf 100644 --- a/examples/layers/widgets/sectors.dart +++ b/examples/layers/widgets/sectors.dart @@ -4,7 +4,7 @@ import 'dart:math' as math; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import '../rendering/src/sector_layout.dart'; diff --git a/examples/layers/widgets/spinning_mixed.dart b/examples/layers/widgets/spinning_mixed.dart index c30e8dab6301a..184d219c11d70 100644 --- a/examples/layers/widgets/spinning_mixed.dart +++ b/examples/layers/widgets/spinning_mixed.dart @@ -2,8 +2,8 @@ // 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/rendering.dart'; +import 'package:material_ui/material_ui.dart'; import '../rendering/src/solid_color_box.dart'; diff --git a/examples/layers/widgets/styled_text.dart b/examples/layers/widgets/styled_text.dart index 4ef75dd47b79a..f8c221d734443 100644 --- a/examples/layers/widgets/styled_text.dart +++ b/examples/layers/widgets/styled_text.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 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; typedef _TextTransformer = Widget Function(String name, String text); diff --git a/pubspec.lock b/pubspec.lock index 8f82714025121..1345c3610b65d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -218,6 +218,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: "427360789a03b76eaf659c37131570d2fc98e381f80d55f252bd9e30f37e7126" + url: "https://pub.dev" + source: hosted + version: "0.0.2" dap: dependency: transitive description: @@ -658,6 +666,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_ui: + dependency: transitive + description: + name: material_ui + sha256: e1fa67393d807bd1841e5ece1ec260ed84f440ae7351e40b6406f733e4fe31cf + url: "https://pub.dev" + source: hosted + version: "0.0.2" meta: dependency: "direct main" description: From f538bd753dfc836556885d9e2d16933917adcd35 Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Thu, 30 Jul 2026 13:23:47 -0700 Subject: [PATCH 122/147] [Flutter GPU] Bind uniforms and textures by cached reflection index (#189820) Fixes https://github.com/flutter/flutter/issues/189819. `RenderPass.bindUniform` and `RenderPass.bindTexture` passed the uniform name across the FFI boundary as a string on every call, and the native side allocated a `std::string` and resolved the binding through a string-keyed map lookup per bind, per draw. `UniformSlot` now resolves the binding's index in the shader's reflection data once, caches it, and binds through new index-taking entry points. `Shader.getUniformSlot` memoizes slots by name so per-draw lookups share the cache, and a reload epoch re-resolves cached indices after a shader library hot reload. The name-keyed entry points remain, and public API behavior is unchanged. In a benchmark submitting ~10k draws per frame (flutter_scene's stress harness, macOS/Metal, profile engine, A/B at the same revision), CPU render time improves 7-12% at p50. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../src/flutter/lib/gpu/lib/src/buffer.dart | 9 +-- .../flutter/lib/gpu/lib/src/render_pass.dart | 37 +++++++--- .../src/flutter/lib/gpu/lib/src/shader.dart | 54 +++++++++++++- .../lib/gpu/lib/src/shader_library.dart | 21 ++++-- engine/src/flutter/lib/gpu/render_pass.cc | 72 ++++++++++++++----- engine/src/flutter/lib/gpu/render_pass.h | 22 ++++++ engine/src/flutter/lib/gpu/shader.cc | 70 ++++++++++++++++++ engine/src/flutter/lib/gpu/shader.h | 33 +++++++++ engine/src/flutter/testing/dart/gpu_test.dart | 28 ++++++++ 9 files changed, 309 insertions(+), 37 deletions(-) diff --git a/engine/src/flutter/lib/gpu/lib/src/buffer.dart b/engine/src/flutter/lib/gpu/lib/src/buffer.dart index df25c61528a6b..e8f1984e79b46 100644 --- a/engine/src/flutter/lib/gpu/lib/src/buffer.dart +++ b/engine/src/flutter/lib/gpu/lib/src/buffer.dart @@ -84,13 +84,14 @@ base class DeviceBuffer extends NativeFieldWrapperClass1 { bool _bindAsUniform( RenderPass renderPass, - UniformSlot slot, + Shader shader, + int uniformStructIndex, int offsetInBytes, int lengthInBytes, ) { - return renderPass._bindUniformDevice( - slot.shader, - slot.uniformName, + return renderPass._bindUniformDeviceIndexed( + shader, + uniformStructIndex, this, offsetInBytes, lengthInBytes, diff --git a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart index a6f42d1d3da1d..040231ae6add8 100644 --- a/engine/src/flutter/lib/gpu/lib/src/render_pass.dart +++ b/engine/src/flutter/lib/gpu/lib/src/render_pass.dart @@ -468,9 +468,18 @@ base class RenderPass extends NativeFieldWrapperClass1 { } void bindUniform(UniformSlot slot, BufferView bufferView) { + // The slot's index is resolved once and cached, so steady-state binds + // pass an integer across the native boundary instead of the name. + int uniformStructIndex = slot._resolvedStructIndex; + if (uniformStructIndex < 0) { + throw Exception( + "Failed to bind uniform (no uniform struct named '${slot.uniformName}')", + ); + } bool success = bufferView.buffer._bindAsUniform( this, - slot, + slot.shader, + uniformStructIndex, bufferView.offsetInBytes, bufferView.lengthInBytes, ); @@ -510,9 +519,15 @@ base class RenderPass extends NativeFieldWrapperClass1 { ); } - bool success = _bindTexture( + int uniformTextureIndex = slot._resolvedTextureIndex; + if (uniformTextureIndex < 0) { + throw Exception( + "Failed to bind texture (no texture named '${slot.uniformName}')", + ); + } + bool success = _bindTextureIndexed( slot.shader, - slot.uniformName, + uniformTextureIndex, texture, sampler.minFilter.index, sampler.magFilter.index, @@ -801,11 +816,11 @@ base class RenderPass extends NativeFieldWrapperClass1 { ); @Native< - Bool Function(Pointer, Pointer, Handle, Pointer, Int, Int) - >(symbol: 'InternalFlutterGpu_RenderPass_BindUniformDevice') - external bool _bindUniformDevice( + Bool Function(Pointer, Pointer, Int, Pointer, Int, Int) + >(symbol: 'InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed') + external bool _bindUniformDeviceIndexed( Shader shader, - String uniformName, + int uniformStructIndex, DeviceBuffer buffer, int offsetInBytes, int lengthInBytes, @@ -827,7 +842,7 @@ base class RenderPass extends NativeFieldWrapperClass1 { Bool Function( Pointer, Pointer, - Handle, + Int, Pointer, Int, Int, @@ -836,10 +851,10 @@ base class RenderPass extends NativeFieldWrapperClass1 { Int, Int, ) - >(symbol: 'InternalFlutterGpu_RenderPass_BindTexture') - external bool _bindTexture( + >(symbol: 'InternalFlutterGpu_RenderPass_BindTextureIndexed') + external bool _bindTextureIndexed( Shader shader, - String uniformName, + int uniformTextureIndex, Texture texture, int minFilter, int magFilter, diff --git a/engine/src/flutter/lib/gpu/lib/src/shader.dart b/engine/src/flutter/lib/gpu/lib/src/shader.dart index 001bfc34671cc..b568cd9b0d148 100644 --- a/engine/src/flutter/lib/gpu/lib/src/shader.dart +++ b/engine/src/flutter/lib/gpu/lib/src/shader.dart @@ -6,11 +6,49 @@ part of flutter_gpu; +/// Bumped whenever any shader library hot reloads. [UniformSlot] caches +/// reflection indices against this epoch, since a reload replaces the +/// shaders' reflection data in place and invalidates cached indices. +int _shaderReloadEpoch = 0; + +const int _kSlotIndexUnresolved = -2; + base class UniformSlot { UniformSlot._(this.shader, this.uniformName); final Shader shader; final String uniformName; + // Reflection indices for the name-free bind path, resolved through one + // native call on first use and cached until a shader hot reload. -1 + // means the shader has no struct/texture with this slot's name. + int _structIndex = _kSlotIndexUnresolved; + int _textureIndex = _kSlotIndexUnresolved; + int _epoch = _shaderReloadEpoch; + + void _syncEpoch() { + if (_epoch != _shaderReloadEpoch) { + _structIndex = _kSlotIndexUnresolved; + _textureIndex = _kSlotIndexUnresolved; + _epoch = _shaderReloadEpoch; + } + } + + int get _resolvedStructIndex { + _syncEpoch(); + if (_structIndex == _kSlotIndexUnresolved) { + _structIndex = shader._getUniformStructIndex(uniformName); + } + return _structIndex; + } + + int get _resolvedTextureIndex { + _syncEpoch(); + if (_textureIndex == _kSlotIndexUnresolved) { + _textureIndex = shader._getUniformTextureIndex(uniformName); + } + return _textureIndex; + } + /// The reflected total size of a shader's uniform struct by name. /// /// Returns [null] if the shader does not contain a uniform struct with the @@ -35,8 +73,12 @@ base class Shader extends NativeFieldWrapperClass1 { // [Shader] handles are instantiated when interacting with a [ShaderLibrary]. Shader._(); + // Memoized so per-draw lookups return the same slot instance, whose + // cached reflection indices make repeat binds name-free. + final Map _uniformSlots = {}; + UniformSlot getUniformSlot(String uniformName) { - return UniformSlot._(this, uniformName); + return _uniformSlots[uniformName] ??= UniformSlot._(this, uniformName); } @Native, Handle)>( @@ -52,6 +94,16 @@ base class Shader extends NativeFieldWrapperClass1 { String memberName, ); + @Native, Handle)>( + symbol: 'InternalFlutterGpu_Shader_GetUniformStructIndex', + ) + external int _getUniformStructIndex(String uniformStructName); + + @Native, Handle)>( + symbol: 'InternalFlutterGpu_Shader_GetUniformTextureIndex', + ) + external int _getUniformTextureIndex(String uniformTextureName); + /// Test-only. Whether this shader is currently marked dirty (will be /// evicted and re-registered with the impeller shader library on next /// pipeline build). Used by tests to assert that reload dedupe keeps diff --git a/engine/src/flutter/lib/gpu/lib/src/shader_library.dart b/engine/src/flutter/lib/gpu/lib/src/shader_library.dart index 22f1a1a64f798..37d5a8480798d 100644 --- a/engine/src/flutter/lib/gpu/lib/src/shader_library.dart +++ b/engine/src/flutter/lib/gpu/lib/src/shader_library.dart @@ -110,14 +110,22 @@ base class ShaderLibrary extends NativeFieldWrapperClass1 { if (error != null) { throw Exception("Failed to reinitialize ShaderLibrary: ${error}"); } + // The reload replaced the shaders' reflection data in place, so cached + // uniform slot indices must re-resolve. + _shaderReloadEpoch++; } /// Test-only. Reloads this library from `assetName`'s bytes while keeping /// this library's identity and registry key. Production hot reload always /// re-fetches the original asset path via [reinitialize]; this hook lets /// tests simulate an edited bundle by swapping in a different fixture. - String? debugReinitializeFromAsset(String assetName) => - _reinitializeWithAsset(assetName); + String? debugReinitializeFromAsset(String assetName) { + final String? error = _reinitializeWithAsset(assetName); + if (error == null) { + _shaderReloadEpoch++; + } + return error; + } /// Reparses [bytes] into this library in place, preserving its identity so /// any [Shader]s already handed out keep working (they are mutated and @@ -127,8 +135,13 @@ base class ShaderLibrary extends NativeFieldWrapperClass1 { /// /// Returns null on success, or an error message if [bytes] could not be /// parsed (the live shaders are left unchanged in that case). - String? reinitializeFromBytes(ByteData bytes) => - _reinitializeWithBytes(bytes); + String? reinitializeFromBytes(ByteData bytes) { + final String? error = _reinitializeWithBytes(bytes); + if (error == null) { + _shaderReloadEpoch++; + } + return error; + } @Native( symbol: 'InternalFlutterGpu_ShaderLibrary_InitializeWithAsset', diff --git a/engine/src/flutter/lib/gpu/render_pass.cc b/engine/src/flutter/lib/gpu/render_pass.cc index 3f2ec0152d49f..cbe5bbc12dd6f 100644 --- a/engine/src/flutter/lib/gpu/render_pass.cc +++ b/engine/src/flutter/lib/gpu/render_pass.cc @@ -405,18 +405,13 @@ void InternalFlutterGpu_RenderPass_BindIndexBufferDevice( length_in_bytes, index_type); } -static bool BindUniform( +static bool BindUniformStruct( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, - Dart_Handle uniform_name_handle, + const flutter::gpu::Shader::UniformBinding* uniform_struct, const std::shared_ptr& buffer, int offset_in_bytes, int length_in_bytes) { - auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); - const flutter::gpu::Shader::UniformBinding* uniform_struct = - shader->GetUniformStruct(uniform_name); - // TODO(bdero): Return an error string stating that no uniform struct with - // this name exists and throw an exception. if (!uniform_struct) { return false; } @@ -458,15 +453,28 @@ bool InternalFlutterGpu_RenderPass_BindUniformDevice( flutter::gpu::DeviceBuffer* device_buffer, int offset_in_bytes, int length_in_bytes) { - return BindUniform(wrapper, shader, uniform_name_handle, - device_buffer->GetBuffer(), offset_in_bytes, - length_in_bytes); + auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); + return BindUniformStruct( + wrapper, shader, shader->GetUniformStruct(uniform_name), + device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes); } -bool InternalFlutterGpu_RenderPass_BindTexture( +bool InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed( flutter::gpu::RenderPass* wrapper, flutter::gpu::Shader* shader, - Dart_Handle uniform_name_handle, + int uniform_struct_index, + flutter::gpu::DeviceBuffer* device_buffer, + int offset_in_bytes, + int length_in_bytes) { + return BindUniformStruct( + wrapper, shader, shader->GetUniformStructAt(uniform_struct_index), + device_buffer->GetBuffer(), offset_in_bytes, length_in_bytes); +} + +static bool BindTextureBinding( + flutter::gpu::RenderPass* wrapper, + flutter::gpu::Shader* shader, + const flutter::gpu::Shader::TextureBinding* texture_binding, flutter::gpu::Texture* texture, int min_filter, int mag_filter, @@ -474,11 +482,6 @@ bool InternalFlutterGpu_RenderPass_BindTexture( int width_address_mode, int height_address_mode, int max_anisotropy) { - auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); - const flutter::gpu::Shader::TextureBinding* texture_binding = - shader->GetUniformTexture(uniform_name); - // TODO(bdero): Return an error string stating that no uniform texture with - // this name exists and throw an exception. if (!texture_binding) { return false; } @@ -520,6 +523,41 @@ bool InternalFlutterGpu_RenderPass_BindTexture( return true; } +bool InternalFlutterGpu_RenderPass_BindTexture( + flutter::gpu::RenderPass* wrapper, + flutter::gpu::Shader* shader, + Dart_Handle uniform_name_handle, + flutter::gpu::Texture* texture, + int min_filter, + int mag_filter, + int mip_filter, + int width_address_mode, + int height_address_mode, + int max_anisotropy) { + auto uniform_name = tonic::StdStringFromDart(uniform_name_handle); + return BindTextureBinding( + wrapper, shader, shader->GetUniformTexture(uniform_name), texture, + min_filter, mag_filter, mip_filter, width_address_mode, + height_address_mode, max_anisotropy); +} + +bool InternalFlutterGpu_RenderPass_BindTextureIndexed( + flutter::gpu::RenderPass* wrapper, + flutter::gpu::Shader* shader, + int uniform_texture_index, + flutter::gpu::Texture* texture, + int min_filter, + int mag_filter, + int mip_filter, + int width_address_mode, + int height_address_mode, + int max_anisotropy) { + return BindTextureBinding( + wrapper, shader, shader->GetUniformTextureAt(uniform_texture_index), + texture, min_filter, mag_filter, mip_filter, width_address_mode, + height_address_mode, max_anisotropy); +} + void InternalFlutterGpu_RenderPass_ClearBindings( flutter::gpu::RenderPass* wrapper) { wrapper->ClearBindings(); diff --git a/engine/src/flutter/lib/gpu/render_pass.h b/engine/src/flutter/lib/gpu/render_pass.h index 2a40293a0b48a..ae72c0b5e6b6c 100644 --- a/engine/src/flutter/lib/gpu/render_pass.h +++ b/engine/src/flutter/lib/gpu/render_pass.h @@ -194,6 +194,15 @@ extern bool InternalFlutterGpu_RenderPass_BindUniformDevice( int offset_in_bytes, int length_in_bytes); +FLUTTER_GPU_EXPORT +extern bool InternalFlutterGpu_RenderPass_BindUniformDeviceIndexed( + flutter::gpu::RenderPass* wrapper, + flutter::gpu::Shader* shader, + int uniform_struct_index, + flutter::gpu::DeviceBuffer* device_buffer, + int offset_in_bytes, + int length_in_bytes); + FLUTTER_GPU_EXPORT extern bool InternalFlutterGpu_RenderPass_BindTexture( flutter::gpu::RenderPass* wrapper, @@ -207,6 +216,19 @@ extern bool InternalFlutterGpu_RenderPass_BindTexture( int height_address_mode, int max_anisotropy); +FLUTTER_GPU_EXPORT +extern bool InternalFlutterGpu_RenderPass_BindTextureIndexed( + flutter::gpu::RenderPass* wrapper, + flutter::gpu::Shader* shader, + int uniform_texture_index, + flutter::gpu::Texture* texture, + int min_filter, + int mag_filter, + int mip_filter, + int width_address_mode, + int height_address_mode, + int max_anisotropy); + FLUTTER_GPU_EXPORT extern void InternalFlutterGpu_RenderPass_ClearBindings( flutter::gpu::RenderPass* wrapper); diff --git a/engine/src/flutter/lib/gpu/shader.cc b/engine/src/flutter/lib/gpu/shader.cc index 31c9470404009..c2ff9954f501a 100644 --- a/engine/src/flutter/lib/gpu/shader.cc +++ b/engine/src/flutter/lib/gpu/shader.cc @@ -58,6 +58,7 @@ fml::RefPtr Shader::Make( shader->uniform_structs_ = std::move(uniform_structs); shader->uniform_textures_ = std::move(uniform_textures); shader->descriptor_set_layouts_ = std::move(descriptor_set_layouts); + shader->RebuildBindingOrder(); return shader; } @@ -110,6 +111,7 @@ void Shader::ResetFrom(Shader& other) { uniform_structs_ = std::move(other.uniform_structs_); uniform_textures_ = std::move(other.uniform_textures_); descriptor_set_layouts_ = std::move(other.descriptor_set_layouts_); + RebuildBindingOrder(); if (code_changed) { is_dirty_ = true; } @@ -191,6 +193,60 @@ const Shader::TextureBinding* Shader::GetUniformTexture( return &uniform->second; } +int Shader::GetUniformStructIndex(const std::string& name) const { + const UniformBinding* binding = GetUniformStruct(name); + if (binding == nullptr) { + return -1; + } + for (size_t i = 0; i < uniform_struct_order_.size(); i++) { + if (uniform_struct_order_[i] == binding) { + return static_cast(i); + } + } + return -1; +} + +const Shader::UniformBinding* Shader::GetUniformStructAt(int index) const { + if (index < 0 || static_cast(index) >= uniform_struct_order_.size()) { + return nullptr; + } + return uniform_struct_order_[index]; +} + +int Shader::GetUniformTextureIndex(const std::string& name) const { + const TextureBinding* binding = GetUniformTexture(name); + if (binding == nullptr) { + return -1; + } + for (size_t i = 0; i < uniform_texture_order_.size(); i++) { + if (uniform_texture_order_[i] == binding) { + return static_cast(i); + } + } + return -1; +} + +const Shader::TextureBinding* Shader::GetUniformTextureAt(int index) const { + if (index < 0 || + static_cast(index) >= uniform_texture_order_.size()) { + return nullptr; + } + return uniform_texture_order_[index]; +} + +void Shader::RebuildBindingOrder() { + uniform_struct_order_.clear(); + uniform_struct_order_.reserve(uniform_structs_.size()); + for (const auto& entry : uniform_structs_) { + uniform_struct_order_.push_back(&entry.second); + } + uniform_texture_order_.clear(); + uniform_texture_order_.reserve(uniform_textures_.size()); + for (const auto& entry : uniform_textures_) { + uniform_texture_order_.push_back(&entry.second); + } +} + } // namespace gpu } // namespace flutter @@ -210,6 +266,20 @@ int InternalFlutterGpu_Shader_GetUniformStructSize( return uniform->size_in_bytes; } +int InternalFlutterGpu_Shader_GetUniformStructIndex( + flutter::gpu::Shader* wrapper, + Dart_Handle struct_name_handle) { + auto name = tonic::StdStringFromDart(struct_name_handle); + return wrapper->GetUniformStructIndex(name); +} + +int InternalFlutterGpu_Shader_GetUniformTextureIndex( + flutter::gpu::Shader* wrapper, + Dart_Handle texture_name_handle) { + auto name = tonic::StdStringFromDart(texture_name_handle); + return wrapper->GetUniformTextureIndex(name); +} + int InternalFlutterGpu_Shader_GetUniformMemberOffset( flutter::gpu::Shader* wrapper, Dart_Handle struct_name_handle, diff --git a/engine/src/flutter/lib/gpu/shader.h b/engine/src/flutter/lib/gpu/shader.h index feae2821a8cf8..b9429bc5f61ba 100644 --- a/engine/src/flutter/lib/gpu/shader.h +++ b/engine/src/flutter/lib/gpu/shader.h @@ -90,6 +90,22 @@ class Shader : public RefCountedDartWrappable { const Shader::TextureBinding* GetUniformTexture( const std::string& name) const; + /// The position of the named uniform struct in this shader's stable + /// binding order, or -1. Indices stay valid until the shader's payload + /// is replaced by a reload (`ResetFrom`); callers cache them to bind + /// without passing the name across the FFI boundary on every draw. + int GetUniformStructIndex(const std::string& name) const; + + /// The uniform struct at `index` in the stable binding order, or nullptr + /// when the index is out of range. + const Shader::UniformBinding* GetUniformStructAt(int index) const; + + /// The texture counterpart to `GetUniformStructIndex`. + int GetUniformTextureIndex(const std::string& name) const; + + /// The texture counterpart to `GetUniformStructAt`. + const Shader::TextureBinding* GetUniformTextureAt(int index) const; + private: Shader(); @@ -105,9 +121,16 @@ class Shader : public RefCountedDartWrappable { std::vector layouts_; std::unordered_map uniform_structs_; std::unordered_map uniform_textures_; + // The maps' entries in a stable order for index-based lookup. Entry + // pointers stay valid for the maps' lifetime (node-based containers); + // rebuilt whenever the maps are replaced (`Make`, `ResetFrom`). + std::vector uniform_struct_order_; + std::vector uniform_texture_order_; std::vector descriptor_set_layouts_; bool is_dirty_ = true; + void RebuildBindingOrder(); + // Returns the scoped name to use when registering or looking up this // shader's function in a shared impeller::ShaderLibrary. std::string GetScopedName() const; @@ -135,6 +158,16 @@ extern int InternalFlutterGpu_Shader_GetUniformMemberOffset( Dart_Handle struct_name_handle, Dart_Handle member_name_handle); +FLUTTER_GPU_EXPORT +extern int InternalFlutterGpu_Shader_GetUniformStructIndex( + flutter::gpu::Shader* wrapper, + Dart_Handle struct_name_handle); + +FLUTTER_GPU_EXPORT +extern int InternalFlutterGpu_Shader_GetUniformTextureIndex( + flutter::gpu::Shader* wrapper, + Dart_Handle texture_name_handle); + // Test-only: exposes the per-shader dirty bit so tests can assert that // reload deduplication keeps unchanged shaders clean. FLUTTER_GPU_EXPORT diff --git a/engine/src/flutter/testing/dart/gpu_test.dart b/engine/src/flutter/testing/dart/gpu_test.dart index 652c337d11897..b31d0ce8c4687 100644 --- a/engine/src/flutter/testing/dart/gpu_test.dart +++ b/engine/src/flutter/testing/dart/gpu_test.dart @@ -1410,6 +1410,34 @@ void main() async { } }, skip: !(impellerEnabled && flutterGpuEnabled)); + test('Shader.getUniformSlot returns the same slot for repeat lookups', () async { + final gpu.RenderPipeline pipeline = await createUnlitRenderPipeline(); + final gpu.UniformSlot first = pipeline.vertexShader.getUniformSlot('VertInfo'); + final gpu.UniformSlot second = pipeline.vertexShader.getUniformSlot('VertInfo'); + expect(identical(first, second), isTrue); + }, skip: !(impellerEnabled && flutterGpuEnabled)); + + test('RenderPass.bindUniform throws for an unknown uniform name', () async { + final RenderPassState state = createSimpleRenderPass(); + + final gpu.RenderPipeline pipeline = await createUnlitRenderPipeline(); + final gpu.DeviceBuffer uniformBuffer = gpu.gpuContext.createDeviceBufferWithCopy( + float32([1, 2, 3, 4]), + ); + final uniformBufferView = gpu.BufferView( + uniformBuffer, + offsetInBytes: 0, + lengthInBytes: uniformBuffer.sizeInBytes, + ); + final gpu.UniformSlot unknownSlot = pipeline.vertexShader.getUniformSlot('DoesNotExist'); + try { + state.renderPass.bindUniform(unknownSlot, uniformBufferView); + fail('Exception not thrown for an unknown uniform name.'); + } catch (e) { + expect(e.toString(), contains('Failed to bind uniform')); + } + }, skip: !(impellerEnabled && flutterGpuEnabled)); + // Renders a green triangle pointing downwards. test('Can render triangle', () async { final RenderPassState state = createSimpleRenderPass(); From 0549766bc8b6d9be0a3267d9aeb9ff762004b93e Mon Sep 17 00:00:00 2001 From: walley892 Date: Thu, 30 Jul 2026 13:31:10 -0700 Subject: [PATCH 123/147] Add nullptr check to isOpaque (#189796) Adds a nullptr check to TiledTextureContents::IsOpaque Likely fixes https://github.com/flutter/flutter/issues/189797, but was unable to reproduce the issue locally ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../entity/contents/tiled_texture_contents.cc | 6 ++++++ .../flutter/impeller/entity/entity_unittests.cc | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc b/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc index 864db9d91b04e..500ec844f1c13 100644 --- a/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc @@ -111,6 +111,9 @@ bool TiledTextureContents::IsOpaque(const Matrix& transform) const { if (color_filter_) { return false; } + if (!texture_) { + return false; + } return texture_->IsOpaque() && !AppliesAlphaForStrokeCoverage(transform); } @@ -223,6 +226,9 @@ std::optional TiledTextureContents::RenderToSnapshot( const ContentContext& renderer, const Entity& entity, const SnapshotOptions& options) const { + if (!texture_) { + return std::nullopt; + } std::optional geometry_coverage = GetGeometry()->GetCoverage({}); if (GetInverseEffectTransform().IsIdentity() && GetGeometry()->IsAxisAlignedRect() && diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index 476af2b6c4402..3ae6e57a03ba8 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -2191,6 +2191,22 @@ TEST_P(EntityTest, TiledTextureContentsIsOpaque) { EXPECT_FALSE(contents.IsOpaque(matrix)); } +TEST_P(EntityTest, TiledTextureContentsIsOpaqueNullTexture) { + Matrix matrix; + auto geom = Geometry::MakeCover(); + TiledTextureContents contents(geom.get()); + contents.SetTexture(nullptr); + EXPECT_FALSE(contents.IsOpaque(matrix)); +} + +TEST_P(EntityTest, TiledTextureContentsRenderToSnapshotNullTexture) { + auto geom = Geometry::MakeCover(); + TiledTextureContents contents(geom.get()); + contents.SetTexture(nullptr); + auto snapshot = contents.RenderToSnapshot(GetContentContext(), Entity(), {}); + EXPECT_FALSE(snapshot.has_value()); +} + TEST_P(EntityTest, PointFieldGeometryCoverage) { std::vector points = {{10, 20}, {100, 200}}; PointFieldGeometry geometry(points.data(), 2, 5.0, false); From e07da4564ef579f2bc4f72d0a8354bde3655e6a6 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 31 Jul 2026 05:37:10 +0900 Subject: [PATCH 124/147] iOS: Drop unnecessary @MainActor from VSyncClientTests (#190285) Remove @MainActor. These tests run VSyncClient on a dedicated worker thread and synchronize with it directly. Nothing in them touches the main thread or its run loop, so the main-actor isolation serves no purpose. This was spotted by @LongCatIsLooong in the review of #190054. ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one PR that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../platform/darwin/ios/framework/Source/VSyncClientTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift index 1a60bb5b378ba..ea09d333686bd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/VSyncClientTests.swift @@ -6,7 +6,6 @@ import Testing @testable import InternalFlutterSwift -@MainActor struct VSyncClientTests { let threadTaskRunner = TaskRunnerTestHelper.makeTaskRunner(withLabel: "VSyncClientTest") From 0a5b3e6123c8fbdf5ebe3eb22429321a08ffcca0 Mon Sep 17 00:00:00 2001 From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:52:05 -0700 Subject: [PATCH 125/147] Migrates the platform_channel example to material_ui (#190253) Migrates the platform_channel example to import material_ui. Related to https://github.com/flutter/flutter/issues/190093. I ran: * dart fix --apply --code=migrate_design_widgets * dart fix --apply --code=directives_ordering ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- dev/bots/check_examples_cross_imports.dart | 1 - examples/platform_channel/lib/main.dart | 2 +- examples/platform_channel/pubspec.yaml | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/bots/check_examples_cross_imports.dart b/dev/bots/check_examples_cross_imports.dart index 8887a3c7fb77f..74968f54f8a0e 100644 --- a/dev/bots/check_examples_cross_imports.dart +++ b/dev/bots/check_examples_cross_imports.dart @@ -585,7 +585,6 @@ class ExamplesCrossImportChecker { 'examples/multiple_windows/lib/app/window_settings_dialog.dart', 'examples/multiple_windows/lib/main.dart', 'examples/multiple_windows/test/multiple_windows_test.dart', - 'examples/platform_channel/lib/main.dart', 'examples/platform_channel_swift/lib/main.dart', 'examples/platform_view/lib/main.dart', 'examples/splash/lib/main.dart', diff --git a/examples/platform_channel/lib/main.dart b/examples/platform_channel/lib/main.dart index 0c057ab13b164..9a47720b6aa5e 100644 --- a/examples/platform_channel/lib/main.dart +++ b/examples/platform_channel/lib/main.dart @@ -4,8 +4,8 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:material_ui/material_ui.dart'; class PlatformChannel extends StatefulWidget { const PlatformChannel({super.key}); diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index c3a0462d9efa1..a52af88d2b27e 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -8,6 +8,7 @@ resolution: workspace dependencies: flutter: sdk: flutter + material_ui: ^0.0.2 dev_dependencies: @@ -21,4 +22,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: f9g2jl +# PUBSPEC CHECKSUM: kujv36 From 31ea49b5ea0d6b8f77dde9b56eb1cc4e928f5109 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:05:01 -0700 Subject: [PATCH 126/147] Migrate swift xctests to Swift testing tests (#187801) Continuation of https://github.com/flutter/flutter/pull/185712. The reason for doing that is I was getting cryptic dynamic actor isolation crashes when trying to turn on Swift 6 language mode for the test target, and switching to swift testing seems to fix that. Most changes are trivial / verbatim, but since Swift Testing by default doesn't run tests on the main thread I had to add `@MainActor` to a few tests to make sure the test has a running `CFRunLoop` ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../Source/AccessibilityFeaturesTests.swift | 4 +- .../Source/DisplayLinkManagerTests.swift | 41 ++++++++--------- .../Source/SplashScreenManagerTests.swift | 45 +++++++++---------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/AccessibilityFeaturesTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/AccessibilityFeaturesTests.swift index f564433a98f59..d0ae629702ad7 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/AccessibilityFeaturesTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/AccessibilityFeaturesTests.swift @@ -78,8 +78,8 @@ struct AccessibilityFeaturesTests { #expect(AccessibilityFeatureFlag.deterministicCursor.rawValue == 1 << 10) } - @Test @MainActor - func flagsBitmaskIsCorrect() { + @MainActor + @Test func flagsBitmaskIsCorrect() { let features = MockAccessibilityFeatures() #expect(features.flags == 0) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift index 198158a1af449..633a730efcd47 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/DisplayLinkManagerTests.swift @@ -2,60 +2,61 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import XCTest +import Testing @testable import InternalFlutterSwift @MainActor -class DisplayLinkManagerTests: XCTestCase { +@Suite struct DisplayLinkManagerTests { - func testDisplayLinkManagerCanBeInstantiatedWithMockValues() { + @Test func displayLinkManagerCanBeInstantiatedWithMockValues() { let manager = DisplayLinkManager(maxRefreshRateEnabled: true, refreshRate: 120.0) - XCTAssertTrue(manager.maxRefreshRateEnabledOnIPhone) - XCTAssertEqual(manager.displayRefreshRate, 120.0) + #expect(manager.maxRefreshRateEnabledOnIPhone) + #expect(manager.displayRefreshRate == 120.0) } - func testDisplayLinkManagerCanBeInstantiatedWithAlternateMockValues() { + @Test func displayLinkManagerCanBeInstantiatedWithAlternateMockValues() { let manager = DisplayLinkManager(maxRefreshRateEnabled: false, refreshRate: 60.0) - XCTAssertFalse(manager.maxRefreshRateEnabledOnIPhone) - XCTAssertEqual(manager.displayRefreshRate, 60.0) + #expect(!manager.maxRefreshRateEnabledOnIPhone) + #expect(manager.displayRefreshRate == 60.0) } - func testSharedInstanceReturnsAValidValue() { + @Test func sharedInstanceReturnsAValidValue() { // Verify that the production shared instance does not crash when accessed in test environment. let shared = DisplayLinkManager.shared - XCTAssertNotNil(shared) - XCTAssertGreaterThan(shared.displayRefreshRate, 0.0) + #expect(shared.displayRefreshRate > 0.0) } - func testSettingDisplayRefreshRateIsReflectedByTheGetter() { + @Test func settingDisplayRefreshRateIsReflectedByTheGetter() { let manager = DisplayLinkManager(maxRefreshRateEnabled: true, refreshRate: 60.0) - XCTAssertEqual(manager.displayRefreshRate, 60.0) + #expect(manager.displayRefreshRate == 60.0) manager.displayRefreshRate = 120.0 - XCTAssertEqual(manager.displayRefreshRate, 120.0) + #expect(manager.displayRefreshRate == 120.0) } - func testDisplayConfigurationNotificationsAreHandledWithoutCrashing() { + @Test func displayConfigurationNotificationsAreHandledWithoutCrashing() async { let shared = DisplayLinkManager.shared - let expectation = expectation(description: "Notification handlers ran on the main queue") NotificationCenter.default.post(name: UIScreen.modeDidChangeNotification, object: UIScreen.main) NotificationCenter.default.post(name: .NSProcessInfoPowerStateDidChange, object: nil) NotificationCenter.default.post( name: ProcessInfo.thermalStateDidChangeNotification, object: nil) NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) - DispatchQueue.main.async { expectation.fulfill() } - wait(for: [expectation], timeout: 1.0) + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + continuation.resume() + } + } // UIScreen.main's reported refresh rate can't be swizzled from a test, so this only // confirms that the notification handlers run to completion without crashing or // deadlocking. The locking/storage behavior they rely on is covered directly by - // testSettingDisplayRefreshRateIsReflectedByTheGetter above. - XCTAssertGreaterThan(shared.displayRefreshRate, 0.0) + // settingDisplayRefreshRateIsReflectedByTheGetter above. + #expect(shared.displayRefreshRate > 0.0) } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SplashScreenManagerTests.swift b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SplashScreenManagerTests.swift index 3065c6dd8f6a7..e938798b72bcd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SplashScreenManagerTests.swift +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SplashScreenManagerTests.swift @@ -3,7 +3,7 @@ // found in the LICENSE file. import UIKit -import XCTest +import Testing @testable import InternalFlutterSwift @@ -31,66 +31,65 @@ final class MockBundle: Bundle, @unchecked Sendable { } } -class SplashScreenManagerTests: XCTestCase { +@MainActor +@Suite struct SplashScreenManagerTests { /// Verifies `loadDefaultSplashScreenView` fails when the `UILaunchStoryboardName` key is missing. - func testLoadDefaultSplashScreenViewFailsWhenNoPlistKey() { + @Test func loadDefaultSplashScreenViewFailsWhenNoPlistKey() { let mockBundle = MockBundle(path: "") let manager = SplashScreenManager(bundle: mockBundle) - XCTAssertFalse(manager.loadDefaultSplashScreenView()) + #expect(!manager.loadDefaultSplashScreenView()) } /// Verifies `loadDefaultSplashScreenView` fails when the storyboard file is not in the bundle. - func testLoadDefaultSplashScreenViewFailsWhenStoryboardNotFound() { + @Test func loadDefaultSplashScreenViewFailsWhenStoryboardNotFound() { let mockBundle = MockBundle(path: "") mockBundle.mockInfoDictionary = ["UILaunchStoryboardName": "LaunchScreen"] let manager = SplashScreenManager(bundle: mockBundle) - XCTAssertFalse(manager.loadDefaultSplashScreenView()) + #expect(!manager.loadDefaultSplashScreenView()) } /// Verifies `setSplashScreenView` sets the view and applies autoresizing masks. - func testSetSplashScreenView() { + @Test func setSplashScreenView() { let manager = SplashScreenManager() let view = UIView() manager.splashScreenView = view - XCTAssertEqual(manager.splashScreenView, view) - XCTAssertEqual(view.autoresizingMask, [.flexibleWidth, .flexibleHeight]) + #expect(manager.splashScreenView == view) + #expect(view.autoresizingMask == [.flexibleWidth, .flexibleHeight]) } /// Verifies setting `splashScreenView` to nil triggers its removal. - func testSetSplashScreenViewToNilRemovesIt() { + @Test func setSplashScreenViewToNilRemovesIt() { let manager = SplashScreenManager() let view = UIView() manager.splashScreenView = view - XCTAssertEqual(manager.splashScreenView, view) + #expect(manager.splashScreenView == view) manager.splashScreenView = nil - XCTAssertNil(manager.splashScreenView) + #expect(manager.splashScreenView == nil) } /// Verifies `removeSplashScreen` calls the completion block after fading out. - func testRemoveSplashScreenCallsCompletion() { + @Test func removeSplashScreenCallsCompletion() async { let manager = SplashScreenManager() let view = UIView() manager.splashScreenView = view - let expectation = self.expectation(description: "Completion called") - - manager.removeSplashScreen { - expectation.fulfill() + await withCheckedContinuation { continuation in + manager.removeSplashScreen { + continuation.resume() + } } - - waitForExpectations(timeout: 1.0, handler: nil) - XCTAssertNil(manager.splashScreenView) + #expect(manager.splashScreenView == nil) } /// Verifies `installSplashScreenView` adds the view to the parent view parent bounds as frame. - func testInstallSplashScreenView() { + @Test func installSplashScreenView() { let manager = SplashScreenManager() let view = UIView() manager.splashScreenView = view @@ -99,7 +98,7 @@ class SplashScreenManagerTests: XCTestCase { manager.installSplashScreenView(asSubviewOf: parentView) - XCTAssertEqual(view.superview, parentView) - XCTAssertEqual(view.frame, parentView.bounds) + #expect(view.superview == parentView) + #expect(view.frame == parentView.bounds) } } From 4119b9f16af2d75bc253087a9c8877194652bd35 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 17:09:58 -0400 Subject: [PATCH 127/147] Roll Skia from 50cf67910462 to a1109355de78 (6 revisions) (#190306) https://skia.googlesource.com/skia.git/+log/50cf67910462..a1109355de78 2026-07-30 robertphillips@google.com Allow exception for PipelineManager for std::mutex and condition_variable 2026-07-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 54cbd6cba08d to 4ffddf5fb2e7 (2 revisions) 2026-07-30 alexisdavidc@google.com [SkCapture] Introduce a flag for enabling capture in viewer 2026-07-30 kjlubick@google.com Make it more clear for agents to use --quiet with ninja 2026-07-30 thomsmit@google.com [graphite] SparseStrips fix Flatten culling 2026-07-30 michaelludwig@google.com [graphite] Test larger transfers in TextureFormatTest If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 416c8f3f65743..e1003bba5ab36 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '50cf67910462acc1e5b6474d0ec9d5f35e44c54c', + 'skia_revision': 'a1109355de783681285de90d047a50109e4c2917', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 7f24a41c4634c576b61232bd6f4aa21f57577b43 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 30 Jul 2026 14:49:24 -0700 Subject: [PATCH 128/147] [flutter/flutter] Migrate multiple_windows example to material_ui (#190313) Migrates the multiple_windows example to material_ui. Part of https://github.com/flutter/flutter/issues/190093. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../multiple_windows/lib/app/dialog_window_content.dart | 2 +- .../lib/app/dialog_window_edit_dialog.dart | 2 +- examples/multiple_windows/lib/app/main_window.dart | 7 ++----- examples/multiple_windows/lib/app/popup_button.dart | 2 +- .../multiple_windows/lib/app/popup_window_content.dart | 2 +- .../multiple_windows/lib/app/popup_window_edit_dialog.dart | 2 +- examples/multiple_windows/lib/app/rotated_wire_cube.dart | 2 +- examples/multiple_windows/lib/app/tooltip_button.dart | 2 +- .../multiple_windows/lib/app/tooltip_window_content.dart | 2 +- .../lib/app/tooltip_window_edit_dialog.dart | 2 +- examples/multiple_windows/lib/app/window_content.dart | 2 +- examples/multiple_windows/lib/app/window_edit_dialog.dart | 2 +- .../multiple_windows/lib/app/window_settings_dialog.dart | 2 +- examples/multiple_windows/lib/main.dart | 2 +- examples/multiple_windows/pubspec.yaml | 3 ++- examples/multiple_windows/test/multiple_windows_test.dart | 2 +- 16 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/multiple_windows/lib/app/dialog_window_content.dart b/examples/multiple_windows/lib/app/dialog_window_content.dart index a35cc95af7ec9..194a7b3f83cce 100644 --- a/examples/multiple_windows/lib/app/dialog_window_content.dart +++ b/examples/multiple_windows/lib/app/dialog_window_content.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'models.dart'; diff --git a/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart b/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart index 442da3f3c5385..671fec36265b0 100644 --- a/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart +++ b/examples/multiple_windows/lib/app/dialog_window_edit_dialog.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; void showDialogWindowEditDialog({ required BuildContext context, diff --git a/examples/multiple_windows/lib/app/main_window.dart b/examples/multiple_windows/lib/app/main_window.dart index a45ab8d759d58..40c023851e9c0 100644 --- a/examples/multiple_windows/lib/app/main_window.dart +++ b/examples/multiple_windows/lib/app/main_window.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'dialog_window_content.dart'; import 'dialog_window_edit_dialog.dart'; @@ -105,10 +105,7 @@ class _WindowsTable extends StatelessWidget { void _showWindowEditDialog(BaseWindowController controller, BuildContext context) { return switch (controller) { - final WindowController regular => showWindowEditDialog( - context: context, - controller: regular, - ), + final WindowController regular => showWindowEditDialog(context: context, controller: regular), final DialogWindowController dialog => showDialogWindowEditDialog( context: context, controller: dialog, diff --git a/examples/multiple_windows/lib/app/popup_button.dart b/examples/multiple_windows/lib/app/popup_button.dart index 343030db338a2..426d7da70a168 100644 --- a/examples/multiple_windows/lib/app/popup_button.dart +++ b/examples/multiple_windows/lib/app/popup_button.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'element_position_tracker.dart'; import 'models.dart'; diff --git a/examples/multiple_windows/lib/app/popup_window_content.dart b/examples/multiple_windows/lib/app/popup_window_content.dart index ab6e242c2ab6c..53769309cb293 100644 --- a/examples/multiple_windows/lib/app/popup_window_content.dart +++ b/examples/multiple_windows/lib/app/popup_window_content.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; class PopupWindowContent extends StatefulWidget { /// Creates a popup window widget. diff --git a/examples/multiple_windows/lib/app/popup_window_edit_dialog.dart b/examples/multiple_windows/lib/app/popup_window_edit_dialog.dart index 10bcd25350e13..a112bfaecda81 100644 --- a/examples/multiple_windows/lib/app/popup_window_edit_dialog.dart +++ b/examples/multiple_windows/lib/app/popup_window_edit_dialog.dart @@ -5,9 +5,9 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; import 'package:flutter/src/widgets/_window_positioner.dart'; +import 'package:material_ui/material_ui.dart'; import 'models.dart'; void showPopupWindowEditDialog({ diff --git a/examples/multiple_windows/lib/app/rotated_wire_cube.dart b/examples/multiple_windows/lib/app/rotated_wire_cube.dart index ad50fe7c6cf3a..2c0ef80f65dc1 100644 --- a/examples/multiple_windows/lib/app/rotated_wire_cube.dart +++ b/examples/multiple_windows/lib/app/rotated_wire_cube.dart @@ -4,7 +4,7 @@ import 'dart:math'; -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:vector_math/vector_math_64.dart'; class RotatedWireCube extends StatefulWidget { diff --git a/examples/multiple_windows/lib/app/tooltip_button.dart b/examples/multiple_windows/lib/app/tooltip_button.dart index d7520a5f04f3b..3f5af0c059ee7 100644 --- a/examples/multiple_windows/lib/app/tooltip_button.dart +++ b/examples/multiple_windows/lib/app/tooltip_button.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'element_position_tracker.dart'; import 'models.dart'; diff --git a/examples/multiple_windows/lib/app/tooltip_window_content.dart b/examples/multiple_windows/lib/app/tooltip_window_content.dart index 1202985b6b065..ee5639c73686e 100644 --- a/examples/multiple_windows/lib/app/tooltip_window_content.dart +++ b/examples/multiple_windows/lib/app/tooltip_window_content.dart @@ -7,8 +7,8 @@ import 'dart:io' show Platform; -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; class TooltipWindowContent extends StatelessWidget { /// Creates a tooltip window widget. diff --git a/examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart b/examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart index 129bc3f725c47..fe74a87b4fbfc 100644 --- a/examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart +++ b/examples/multiple_windows/lib/app/tooltip_window_edit_dialog.dart @@ -5,9 +5,9 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; import 'package:flutter/src/widgets/_window_positioner.dart'; +import 'package:material_ui/material_ui.dart'; import 'models.dart'; void showTooltipWindowEditDialog({ diff --git a/examples/multiple_windows/lib/app/window_content.dart b/examples/multiple_windows/lib/app/window_content.dart index 393c3be452f20..7d79b1f0f4143 100644 --- a/examples/multiple_windows/lib/app/window_content.dart +++ b/examples/multiple_windows/lib/app/window_content.dart @@ -7,8 +7,8 @@ import 'dart:math'; -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'dialog_window_content.dart'; import 'models.dart'; diff --git a/examples/multiple_windows/lib/app/window_edit_dialog.dart b/examples/multiple_windows/lib/app/window_edit_dialog.dart index 0a77718971713..996218788f1d4 100644 --- a/examples/multiple_windows/lib/app/window_edit_dialog.dart +++ b/examples/multiple_windows/lib/app/window_edit_dialog.dart @@ -5,8 +5,8 @@ // ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports -import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; void showWindowEditDialog({ required BuildContext context, diff --git a/examples/multiple_windows/lib/app/window_settings_dialog.dart b/examples/multiple_windows/lib/app/window_settings_dialog.dart index f01e4e05b806a..d32803604bf0a 100644 --- a/examples/multiple_windows/lib/app/window_settings_dialog.dart +++ b/examples/multiple_windows/lib/app/window_settings_dialog.dart @@ -4,9 +4,9 @@ // ignore_for_file: invalid_use_of_internal_member -import 'package:flutter/material.dart'; // ignore: implementation_imports import 'package:flutter/src/widgets/_window_positioner.dart'; +import 'package:material_ui/material_ui.dart'; import 'models.dart'; diff --git a/examples/multiple_windows/lib/main.dart b/examples/multiple_windows/lib/main.dart index b35e4a28e0691..3b698a56260d8 100644 --- a/examples/multiple_windows/lib/main.dart +++ b/examples/multiple_windows/lib/main.dart @@ -7,9 +7,9 @@ import 'dart:ui'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/src/widgets/_window.dart'; +import 'package:material_ui/material_ui.dart'; import 'app/main_window.dart'; import 'app/models.dart'; diff --git a/examples/multiple_windows/pubspec.yaml b/examples/multiple_windows/pubspec.yaml index f0a6a2771a213..9cf00a98ec67c 100644 --- a/examples/multiple_windows/pubspec.yaml +++ b/examples/multiple_windows/pubspec.yaml @@ -6,6 +6,7 @@ environment: dependencies: flutter: sdk: flutter + material_ui: ^0.0.2 vector_math: ^2.2.0 dev_dependencies: @@ -16,4 +17,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: dpf2lg +# PUBSPEC CHECKSUM: mej66b diff --git a/examples/multiple_windows/test/multiple_windows_test.dart b/examples/multiple_windows/test/multiple_windows_test.dart index 5c279a9128739..fb7608aa18159 100644 --- a/examples/multiple_windows/test/multiple_windows_test.dart +++ b/examples/multiple_windows/test/multiple_windows_test.dart @@ -4,9 +4,9 @@ // ignore_for_file: invalid_use_of_internal_member -import 'package:flutter/material.dart'; import 'package:flutter/src/foundation/_features.dart' show isWindowingEnabled; import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart'; // ignore: avoid_relative_lib_imports import '../lib/main.dart' as multiple_windows; From 0c52dee3bc3653a6fefd86409b68f4e10cc4194f Mon Sep 17 00:00:00 2001 From: ahyang <41765610+ahyangnb@users.noreply.github.com> Date: Fri, 31 Jul 2026 07:18:26 +0800 Subject: [PATCH 129/147] [Impeller] Add filterQuality support to ImageFilter.shader (#188544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `filterQuality` argument to `ImageFilter.shader` so the implicitly bound image-filter input sampler can be configured instead of always using nearest-neighbor sampling. This threads the requested sampling quality through `dart:ui`, DisplayList runtime-effect image filters, and Impeller’s runtime-effect texture input setup. The default remains `FilterQuality.none` to preserve existing behavior. This also adds regression coverage with a fractional-texel shader to verify that `BackdropFilter` + `ImageFilter.shader` honors `FilterQuality`, plus equality coverage for shader image filters that differ only by input sampling. Fixes https://github.com/flutter/flutter/issues/188365 No changes were required in the `flutter/tests` repo. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: b-luk <97480502+b-luk@users.noreply.github.com> --- .../display_list/effects/dl_image_filter.cc | 6 +- .../display_list/effects/dl_image_filter.h | 3 +- .../effects/dl_image_filter_unittests.cc | 15 ++ .../dl_runtime_effect_image_filter.cc | 9 +- .../dl_runtime_effect_image_filter.h | 15 +- .../impeller/display_list/image_filter.cc | 11 +- .../fixtures/shaders/general_shaders/BUILD.gn | 1 + .../filter_shader_fractional_texel.frag | 15 ++ engine/src/flutter/lib/ui/painting.dart | 24 ++- .../lib/ui/painting/fragment_program.cc | 7 +- .../lib/ui/painting/fragment_program.h | 3 +- .../lib/ui/painting/fragment_shader.cc | 6 +- .../flutter/lib/ui/painting/fragment_shader.h | 3 +- .../flutter/lib/ui/painting/image_filter.cc | 5 +- .../flutter/lib/ui/painting/image_filter.h | 2 +- .../src/flutter/lib/web_ui/lib/painting.dart | 8 +- .../testing/dart/fragment_shader_test.dart | 5 + .../flutter/testing/dart/painting_test.dart | 186 ++++++++++++------ 18 files changed, 230 insertions(+), 94 deletions(-) create mode 100644 engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/filter_shader_fractional_texel.frag diff --git a/engine/src/flutter/display_list/effects/dl_image_filter.cc b/engine/src/flutter/display_list/effects/dl_image_filter.cc index 752baf6ae4e66..1a66b7664f999 100644 --- a/engine/src/flutter/display_list/effects/dl_image_filter.cc +++ b/engine/src/flutter/display_list/effects/dl_image_filter.cc @@ -34,9 +34,11 @@ std::shared_ptr DlImageFilter::MakeMatrix( std::shared_ptr DlImageFilter::MakeRuntimeEffect( sk_sp runtime_effect, std::vector> samplers, - std::shared_ptr> uniform_data) { + std::shared_ptr> uniform_data, + DlImageSampling input_sampling) { return DlRuntimeEffectImageFilter::Make( - std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); + std::move(runtime_effect), std::move(samplers), std::move(uniform_data), + input_sampling); } std::shared_ptr DlImageFilter::MakeColorFilter( diff --git a/engine/src/flutter/display_list/effects/dl_image_filter.h b/engine/src/flutter/display_list/effects/dl_image_filter.h index 911fe62778247..8104c54de85c1 100644 --- a/engine/src/flutter/display_list/effects/dl_image_filter.h +++ b/engine/src/flutter/display_list/effects/dl_image_filter.h @@ -67,7 +67,8 @@ class DlImageFilter : public DlAttribute { static std::shared_ptr MakeRuntimeEffect( sk_sp runtime_effect, std::vector> samplers, - std::shared_ptr> uniform_data); + std::shared_ptr> uniform_data, + DlImageSampling input_sampling = DlImageSampling::kNearestNeighbor); static std::shared_ptr MakeColorFilter( const std::shared_ptr& filter); diff --git a/engine/src/flutter/display_list/effects/dl_image_filter_unittests.cc b/engine/src/flutter/display_list/effects/dl_image_filter_unittests.cc index aedbed332bb37..de5c1daa052b7 100644 --- a/engine/src/flutter/display_list/effects/dl_image_filter_unittests.cc +++ b/engine/src/flutter/display_list/effects/dl_image_filter_unittests.cc @@ -907,6 +907,21 @@ TEST(DisplayListImageFilter, RuntimeEffectEquality) { EXPECT_NE(filter_a, filter_c); } +TEST(DisplayListImageFilter, RuntimeEffectEqualityWithInputSampling) { + DlRuntimeEffectImageFilter filter_a(nullptr, {nullptr}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_b(nullptr, {nullptr}, + std::make_shared>()); + DlRuntimeEffectImageFilter filter_c(nullptr, {nullptr}, + std::make_shared>(), + DlImageSampling::kLinear); + + EXPECT_EQ(filter_a.input_sampling(), DlImageSampling::kNearestNeighbor); + EXPECT_EQ(filter_c.input_sampling(), DlImageSampling::kLinear); + EXPECT_EQ(filter_a, filter_b); + EXPECT_NE(filter_a, filter_c); +} + TEST(DisplayListImageFilter, RuntimeEffectEqualityWithSamplers) { auto image_a = DlColorSource::MakeImage(nullptr, DlTileMode::kClamp, DlTileMode::kDecal); diff --git a/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc b/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc index 9c6c285532d61..d2c52f10ce4d9 100644 --- a/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc +++ b/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.cc @@ -9,9 +9,11 @@ namespace flutter { std::shared_ptr DlRuntimeEffectImageFilter::Make( sk_sp runtime_effect, std::vector> samplers, - std::shared_ptr> uniform_data) { + std::shared_ptr> uniform_data, + DlImageSampling input_sampling) { return std::make_shared( - std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); + std::move(runtime_effect), std::move(samplers), std::move(uniform_data), + input_sampling); } DlRect* DlRuntimeEffectImageFilter::map_local_bounds( @@ -42,7 +44,8 @@ bool DlRuntimeEffectImageFilter::equals_(const DlImageFilter& other) const { auto that = static_cast(&other); if (runtime_effect_ != that->runtime_effect_ || samplers_.size() != that->samplers().size() || - uniform_data_->size() != that->uniform_data()->size()) { + uniform_data_->size() != that->uniform_data()->size() || + input_sampling_ != that->input_sampling()) { return false; } for (auto i = 0u; i < samplers_.size(); i++) { diff --git a/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h b/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h index 391cfd9f475f9..ec72e58430b94 100644 --- a/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h +++ b/engine/src/flutter/display_list/effects/image_filters/dl_runtime_effect_image_filter.h @@ -17,20 +17,24 @@ class DlRuntimeEffectImageFilter final : public DlImageFilter { explicit DlRuntimeEffectImageFilter( sk_sp runtime_effect, std::vector> samplers, - std::shared_ptr> uniform_data) + std::shared_ptr> uniform_data, + DlImageSampling input_sampling = DlImageSampling::kNearestNeighbor) : runtime_effect_(std::move(runtime_effect)), samplers_(std::move(samplers)), - uniform_data_(std::move(uniform_data)) {} + uniform_data_(std::move(uniform_data)), + input_sampling_(input_sampling) {} std::shared_ptr shared() const override { return std::make_shared( - this->runtime_effect_, this->samplers_, this->uniform_data_); + this->runtime_effect_, this->samplers_, this->uniform_data_, + this->input_sampling_); } static std::shared_ptr Make( sk_sp runtime_effect, std::vector> samplers, - std::shared_ptr> uniform_data); + std::shared_ptr> uniform_data, + DlImageSampling input_sampling = DlImageSampling::kNearestNeighbor); DlImageFilterType type() const override { return DlImageFilterType::kRuntimeEffect; @@ -66,6 +70,8 @@ class DlRuntimeEffectImageFilter final : public DlImageFilter { return uniform_data_; } + DlImageSampling input_sampling() const { return input_sampling_; } + protected: bool equals_(const DlImageFilter& other) const override; @@ -73,6 +79,7 @@ class DlRuntimeEffectImageFilter final : public DlImageFilter { sk_sp runtime_effect_; std::vector> samplers_; std::shared_ptr> uniform_data_; + DlImageSampling input_sampling_; }; } // namespace flutter diff --git a/engine/src/flutter/impeller/display_list/image_filter.cc b/engine/src/flutter/impeller/display_list/image_filter.cc index e87964a9d5b37..06981f432b575 100644 --- a/engine/src/flutter/impeller/display_list/image_filter.cc +++ b/engine/src/flutter/impeller/display_list/image_filter.cc @@ -117,13 +117,15 @@ std::shared_ptr WrapInput(const ContentContext& renderer, runtime_filter->runtime_effect()->runtime_stage(); std::vector texture_inputs; - size_t index = 0; + bool is_first = true; for (const std::shared_ptr& sampler : runtime_filter->samplers()) { - if (index == 0 && sampler == nullptr) { - // Insert placeholder for filter. + if (is_first) { + is_first = false; + // The first sampler is always the image filter input. texture_inputs.push_back( - {.sampler_descriptor = skia_conversions::ToSamplerDescriptor({}), + {.sampler_descriptor = skia_conversions::ToSamplerDescriptor( + runtime_filter->input_sampling()), .texture = nullptr}); continue; } @@ -137,7 +139,6 @@ std::shared_ptr WrapInput(const ContentContext& renderer, std::shared_ptr texture = image->image()->asImpellerImage()->GetCachedTexture(renderer); FML_DCHECK(texture); - index++; texture_inputs.push_back({ .sampler_descriptor = skia_conversions::ToSamplerDescriptor(image->sampling()), diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn index 8b37a74565d27..8b20c28fc2b0e 100644 --- a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/BUILD.gn @@ -13,6 +13,7 @@ if (enable_unittests) { "circle_sdf.frag", "double_sampler_swapped.frag", "double_sampler.frag", + "filter_shader_fractional_texel.frag", "filter_shader.frag", "functions.frag", "missing_size.frag", diff --git a/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/filter_shader_fractional_texel.frag b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/filter_shader_fractional_texel.frag new file mode 100644 index 0000000000000..30a158d8c72ec --- /dev/null +++ b/engine/src/flutter/lib/ui/fixtures/shaders/general_shaders/filter_shader_fractional_texel.frag @@ -0,0 +1,15 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +uniform vec2 u_size; +uniform sampler2D u_texture; + +out vec4 frag_color; + +void main() { + vec2 uv = (FlutterFragCoord().xy + vec2(0.25, 0.0)) / u_size; + frag_color = texture(u_texture, uv); +} diff --git a/engine/src/flutter/lib/ui/painting.dart b/engine/src/flutter/lib/ui/painting.dart index 1268cba684c01..e1cada240c761 100644 --- a/engine/src/flutter/lib/ui/painting.dart +++ b/engine/src/flutter/lib/ui/painting.dart @@ -4429,6 +4429,9 @@ abstract class ImageFilter { /// also be at least one sampler2D uniform, the first of which will be set by /// the engine to contain the filter input. /// + /// The optional [filterQuality] argument sets the quality level used to sample + /// the filter input. By default, it is set to [FilterQuality.none]. + /// /// When Impeller uses the OpenGL(ES) backend, the y-axis direction is /// reversed. Custom fragment shaders must invert the y-axis on /// GLES or they will render upside-down. @@ -4458,7 +4461,10 @@ abstract class ImageFilter { /// } /// /// ``` - factory ImageFilter.shader(FragmentShader shader) { + factory ImageFilter.shader( + FragmentShader shader, { + FilterQuality filterQuality = FilterQuality.none, + }) { if (!_impellerEnabled) { throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); } @@ -4477,7 +4483,7 @@ abstract class ImageFilter { } throw StateError(buffer.toString()); } - return _FragmentShaderImageFilter(shader); + return _FragmentShaderImageFilter(shader, filterQuality); } /// Whether [ImageFilter.shader] is supported on the current backend. @@ -4673,9 +4679,10 @@ class _ComposeImageFilter implements ImageFilter { } class _FragmentShaderImageFilter implements ImageFilter { - _FragmentShaderImageFilter(this.shader); + _FragmentShaderImageFilter(this.shader, this.filterQuality); final FragmentShader shader; + final FilterQuality filterQuality; late final _ImageFilter nativeFilter = _ImageFilter.shader(this); @@ -4686,7 +4693,7 @@ class _FragmentShaderImageFilter implements ImageFilter { String get debugShortDescription => 'shader'; @override - String toString() => 'ImageFilter.shader(Shader#${shader.hashCode})'; + String toString() => 'ImageFilter.shader(Shader#${shader.hashCode}, $filterQuality)'; @override bool operator ==(Object other) { @@ -4695,6 +4702,7 @@ class _FragmentShaderImageFilter implements ImageFilter { } return other is _FragmentShaderImageFilter && other.shader == shader && + other.filterQuality == filterQuality && _equals(nativeFilter, other.nativeFilter); } @@ -4702,7 +4710,7 @@ class _FragmentShaderImageFilter implements ImageFilter { external static bool _equals(_ImageFilter a, _ImageFilter b); @override - int get hashCode => shader.hashCode; + int get hashCode => Object.hash(shader, filterQuality); } /// An [ImageFilter] that is backed by a native DlImageFilter. @@ -4770,7 +4778,7 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { _ImageFilter.shader(_FragmentShaderImageFilter filter) : creator = filter { _constructor(); - _initShader(filter.shader); + _initShader(filter.shader, filter.filterQuality.index); } @Native(symbol: 'ImageFilter::Create') @@ -4813,8 +4821,8 @@ base class _ImageFilter extends NativeFieldWrapperClass1 { ) external void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter); - @Native, Pointer)>(symbol: 'ImageFilter::initShader') - external void _initShader(FragmentShader shader); + @Native, Pointer, Int32)>(symbol: 'ImageFilter::initShader') + external void _initShader(FragmentShader shader, int filterQuality); /// The original Dart object that created the native wrapper, which retains /// the values used for the filter. diff --git a/engine/src/flutter/lib/ui/painting/fragment_program.cc b/engine/src/flutter/lib/ui/painting/fragment_program.cc index 74f4a33c66ae8..a9b8c454f4d64 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_program.cc +++ b/engine/src/flutter/lib/ui/painting/fragment_program.cc @@ -271,9 +271,10 @@ std::shared_ptr FragmentProgram::MakeDlColorSource( std::shared_ptr FragmentProgram::MakeDlImageFilter( std::shared_ptr> float_uniforms, - const std::vector>& children) { - return DlImageFilter::MakeRuntimeEffect(runtime_effect_, children, - std::move(float_uniforms)); + const std::vector>& children, + DlImageSampling input_sampling) { + return DlImageFilter::MakeRuntimeEffect( + runtime_effect_, children, std::move(float_uniforms), input_sampling); } void FragmentProgram::Create(Dart_Handle wrapper) { diff --git a/engine/src/flutter/lib/ui/painting/fragment_program.h b/engine/src/flutter/lib/ui/painting/fragment_program.h index 65eb460658c51..def8c4787f802 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_program.h +++ b/engine/src/flutter/lib/ui/painting/fragment_program.h @@ -41,7 +41,8 @@ class FragmentProgram : public RefCountedDartWrappable { std::shared_ptr MakeDlImageFilter( std::shared_ptr> float_uniforms, - const std::vector>& children); + const std::vector>& children, + DlImageSampling input_sampling = DlImageSampling::kNearestNeighbor); private: FragmentProgram(); diff --git a/engine/src/flutter/lib/ui/painting/fragment_shader.cc b/engine/src/flutter/lib/ui/painting/fragment_shader.cc index 7ce2f25a88c9e..bf308846c63fd 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_shader.cc +++ b/engine/src/flutter/lib/ui/painting/fragment_shader.cc @@ -94,7 +94,8 @@ void ReusableFragmentShader::SetImageSampler(Dart_Handle index_handle, uniform_floats[float_count_ + 2 * index + 1] = image->height(); } -std::shared_ptr ReusableFragmentShader::as_image_filter() const { +std::shared_ptr ReusableFragmentShader::as_image_filter( + DlImageSampling input_sampling) const { FML_CHECK(program_); // The lifetime of this object is longer than a frame, and the uniforms can be @@ -104,7 +105,8 @@ std::shared_ptr ReusableFragmentShader::as_image_filter() const { uniform_data->resize(uniform_data_->size()); memcpy(uniform_data->data(), uniform_data_->bytes(), uniform_data->size()); - return program_->MakeDlImageFilter(std::move(uniform_data), samplers_); + return program_->MakeDlImageFilter(std::move(uniform_data), samplers_, + input_sampling); } std::shared_ptr ReusableFragmentShader::shader( diff --git a/engine/src/flutter/lib/ui/painting/fragment_shader.h b/engine/src/flutter/lib/ui/painting/fragment_shader.h index 2246dc5093e8c..60efb487f8b8c 100644 --- a/engine/src/flutter/lib/ui/painting/fragment_shader.h +++ b/engine/src/flutter/lib/ui/painting/fragment_shader.h @@ -47,7 +47,8 @@ class ReusableFragmentShader : public Shader { // |Shader| std::shared_ptr shader(DlImageSampling) override; - std::shared_ptr as_image_filter() const; + std::shared_ptr as_image_filter( + DlImageSampling input_sampling) const; private: ReusableFragmentShader(fml::RefPtr program, diff --git a/engine/src/flutter/lib/ui/painting/image_filter.cc b/engine/src/flutter/lib/ui/painting/image_filter.cc index 41366bbe38c09..3fb577980e1f5 100644 --- a/engine/src/flutter/lib/ui/painting/image_filter.cc +++ b/engine/src/flutter/lib/ui/painting/image_filter.cc @@ -131,9 +131,10 @@ void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) { inner->filter(DlTileMode::kClamp)); } -void ImageFilter::initShader(ReusableFragmentShader* shader) { +void ImageFilter::initShader(ReusableFragmentShader* shader, + int filterQualityIndex) { FML_DCHECK(shader); - filter_ = shader->as_image_filter(); + filter_ = shader->as_image_filter(SamplingFromIndex(filterQualityIndex)); } bool ImageFilter::equals(Dart_Handle a_handle, Dart_Handle b_handle) { diff --git a/engine/src/flutter/lib/ui/painting/image_filter.h b/engine/src/flutter/lib/ui/painting/image_filter.h index 18a71c02e7bc1..47449193119f2 100644 --- a/engine/src/flutter/lib/ui/painting/image_filter.h +++ b/engine/src/flutter/lib/ui/painting/image_filter.h @@ -42,7 +42,7 @@ class ImageFilter : public RefCountedDartWrappable { void initMatrix(const tonic::Float64List& matrix4, int filter_quality_index); void initColorFilter(ColorFilter* colorFilter); void initComposeFilter(ImageFilter* outer, ImageFilter* inner); - void initShader(ReusableFragmentShader* shader); + void initShader(ReusableFragmentShader* shader, int filter_quality_index); static bool equals(Dart_Handle a_handle, Dart_Handle b_handle); const std::shared_ptr filter(DlTileMode mode) const; diff --git a/engine/src/flutter/lib/web_ui/lib/painting.dart b/engine/src/flutter/lib/web_ui/lib/painting.dart index 73f133e97fa75..94ddea19142fe 100644 --- a/engine/src/flutter/lib/web_ui/lib/painting.dart +++ b/engine/src/flutter/lib/web_ui/lib/painting.dart @@ -690,8 +690,12 @@ class ImageFilter { factory ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) => engine.renderer.composeImageFilters(outer: outer, inner: inner); - // ignore: avoid_unused_constructor_parameters - factory ImageFilter.shader(FragmentShader shader) { + factory ImageFilter.shader( + // ignore: avoid_unused_constructor_parameters + FragmentShader shader, { + // ignore: avoid_unused_constructor_parameters + FilterQuality filterQuality = FilterQuality.none, + }) { throw UnsupportedError('ImageFilter.shader only supported with Impeller rendering engine.'); } diff --git a/engine/src/flutter/testing/dart/fragment_shader_test.dart b/engine/src/flutter/testing/dart/fragment_shader_test.dart index e130b65ac7d31..79edd1b85e555 100644 --- a/engine/src/flutter/testing/dart/fragment_shader_test.dart +++ b/engine/src/flutter/testing/dart/fragment_shader_test.dart @@ -1630,6 +1630,11 @@ void main() async { expect(filter, filter_2); expect(identical(filter, filter_2), false); + final filterLowQuality = ImageFilter.shader(shader, filterQuality: FilterQuality.low); + expect(filter, isNot(filterLowQuality)); + expect(filterLowQuality, ImageFilter.shader(shader, filterQuality: FilterQuality.low)); + expect(identical(filter, filterLowQuality), false); + shader.setFloat(0, 1); final filter_3 = ImageFilter.shader(shader); diff --git a/engine/src/flutter/testing/dart/painting_test.dart b/engine/src/flutter/testing/dart/painting_test.dart index dc83c2b1db12a..d8a6b2b87c800 100644 --- a/engine/src/flutter/testing/dart/painting_test.dart +++ b/engine/src/flutter/testing/dart/painting_test.dart @@ -9,6 +9,7 @@ import 'package:test/test.dart'; import 'package:vector_math/vector_math_64.dart'; import 'goldens.dart'; +import 'impeller_enabled.dart'; typedef CanvasCallback = void Function(Canvas canvas); @@ -94,16 +95,9 @@ void main() { test('BackdropFilter with multiple clips', () async { // Regression test for https://github.com/flutter/flutter/issues/144211 - Picture makePicture(CanvasCallback callback) { - final recorder = PictureRecorder(); - final canvas = Canvas(recorder); - callback(canvas); - return recorder.endRecording(); - } - final sceneBuilder = SceneBuilder(); - final Picture redClippedPicture = makePicture((Canvas canvas) { + final Picture redClippedPicture = _makePicture((Canvas canvas) { canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF)); canvas.clipRect(const Rect.fromLTRB(10, 10, 200, 200)); canvas.clipRect(const Rect.fromLTRB(11, 10, 300, 200)); @@ -114,7 +108,7 @@ void main() { final matrix = Float64List(16); sceneBuilder.pushBackdropFilter(ImageFilter.matrix(matrix)); - final Picture whitePicture = makePicture((Canvas canvas) { + final Picture whitePicture = _makePicture((Canvas canvas) { canvas.drawPaint(Paint()..color = const Color(0xFFFFFFFF)); }); sceneBuilder.addPicture(Offset.zero, whitePicture); @@ -135,59 +129,34 @@ void main() { redClippedPicture.dispose(); }); - Image backdropBlurWithTileMode(TileMode? tileMode) { - Picture makePicture(CanvasCallback callback) { - final recorder = PictureRecorder(); - final canvas = Canvas(recorder); - callback(canvas); - return recorder.endRecording(); + test('BackdropFilter with ImageFilter.shader honors FilterQuality', () async { + // Regression test for https://github.com/flutter/flutter/issues/188365. + if (!impellerEnabled) { + print('Skipped for Skia.'); + return; } - const double rectSize = 10; - const count = 50; - const double imgSize = rectSize * count; - - final Picture blueGreenGridPicture = makePicture((Canvas canvas) { - const white = Color(0xFFFFFFFF); - const purple = Color(0xFFFF00FF); - const blue = Color(0xFF0000FF); - const green = Color(0xFF00FF00); - const yellow = Color(0xFFFFFF00); - const red = Color(0xFFFF0000); - canvas.drawColor(white, BlendMode.src); - for (var i = 0; i < count; i++) { - for (var j = 0; j < count; j++) { - final rectOdd = (i + j) & 1 == 0; - final fg = (i < count / 2) - ? ((j < count / 2) ? green : blue) - : ((j < count / 2) ? yellow : red); - canvas.drawRect( - Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize), - Paint()..color = rectOdd ? fg : white, - ); - } - } - canvas.drawRect(const Rect.fromLTWH(0, 0, imgSize, 1), Paint()..color = purple); - canvas.drawRect(const Rect.fromLTWH(0, 0, 1, imgSize), Paint()..color = purple); - canvas.drawRect(const Rect.fromLTWH(0, imgSize - 1, imgSize, 1), Paint()..color = purple); - canvas.drawRect(const Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), Paint()..color = purple); - }); - - final sceneBuilder = SceneBuilder(); - sceneBuilder.addPicture(Offset.zero, blueGreenGridPicture); - sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode)); + // The helper draws a black/white striped backdrop and filters it with a + // shader that samples the backdrop at a fractional texel coordinate. Because + // the stripes are grayscale, checking a single color channel is enough to + // tell whether the sample came from a source texel or was interpolated. + final Image nearest = await _backdropShaderWithFilterQuality(FilterQuality.none); + final Image linear = await _backdropShaderWithFilterQuality(FilterQuality.low); - final Scene scene = sceneBuilder.build(); - final Image image = scene.toImageSync(imgSize.round(), imgSize.round()); + // Nearest-neighbor sampling should pick either black or white exactly. + // Linear sampling should blend the adjacent black and white stripes. + final int nearestSample = await _redAt(nearest, 0, 1); + final int linearSample = await _redAt(linear, 0, 1); - scene.dispose(); - blueGreenGridPicture.dispose(); + expect(nearestSample, anyOf(0, 255)); + expect(linearSample, allOf(greaterThan(0), lessThan(255))); - return image; - } + nearest.dispose(); + linear.dispose(); + }); test('BackdropFilter with Blur honors TileMode.decal', () async { - final Image image = backdropBlurWithTileMode(TileMode.decal); + final Image image = _backdropBlurWithTileMode(TileMode.decal); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_decal_tile_mode.png'); @@ -196,7 +165,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.clamp', () async { - final Image image = backdropBlurWithTileMode(TileMode.clamp); + final Image image = _backdropBlurWithTileMode(TileMode.clamp); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_clamp_tile_mode.png'); @@ -205,7 +174,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.mirror', () async { - final Image image = backdropBlurWithTileMode(TileMode.mirror); + final Image image = _backdropBlurWithTileMode(TileMode.mirror); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_mirror_tile_mode.png'); @@ -214,7 +183,7 @@ void main() { }); test('BackdropFilter with Blur honors TileMode.repeated', () async { - final Image image = backdropBlurWithTileMode(TileMode.repeated); + final Image image = _backdropBlurWithTileMode(TileMode.repeated); final ImageComparer comparer = await ImageComparer.create(); await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_repeated_tile_mode.png'); @@ -223,7 +192,7 @@ void main() { }); test('BackdropFilter with Blur default TileMode acts as TileMode.mirror', () async { - final Image image = backdropBlurWithTileMode(null); + final Image image = _backdropBlurWithTileMode(null); final ImageComparer comparer = await ImageComparer.create(); // It would be nice to compare the output here to the "mirror" golden @@ -278,3 +247,102 @@ void main() { } }); } + +Picture _makePicture(CanvasCallback callback) { + final recorder = PictureRecorder(); + final canvas = Canvas(recorder); + callback(canvas); + return recorder.endRecording(); +} + +Image _backdropBlurWithTileMode(TileMode? tileMode) { + const double rectSize = 10; + const count = 50; + const double imgSize = rectSize * count; + + final Picture blueGreenGridPicture = _makePicture((Canvas canvas) { + const white = Color(0xFFFFFFFF); + const purple = Color(0xFFFF00FF); + const blue = Color(0xFF0000FF); + const green = Color(0xFF00FF00); + const yellow = Color(0xFFFFFF00); + const red = Color(0xFFFF0000); + canvas.drawColor(white, BlendMode.src); + for (var i = 0; i < count; i++) { + for (var j = 0; j < count; j++) { + final rectOdd = (i + j) & 1 == 0; + final fg = (i < count / 2) + ? ((j < count / 2) ? green : blue) + : ((j < count / 2) ? yellow : red); + canvas.drawRect( + Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize), + Paint()..color = rectOdd ? fg : white, + ); + } + } + canvas.drawRect(const Rect.fromLTWH(0, 0, imgSize, 1), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(0, 0, 1, imgSize), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(0, imgSize - 1, imgSize, 1), Paint()..color = purple); + canvas.drawRect(const Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), Paint()..color = purple); + }); + + final sceneBuilder = SceneBuilder(); + sceneBuilder.addPicture(Offset.zero, blueGreenGridPicture); + sceneBuilder.pushBackdropFilter(ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode)); + + final Scene scene = sceneBuilder.build(); + final Image image = scene.toImageSync(imgSize.round(), imgSize.round()); + + scene.dispose(); + blueGreenGridPicture.dispose(); + + return image; +} + +Future _backdropShaderWithFilterQuality(FilterQuality filterQuality) async { + const width = 16; + const height = 4; + const stripeWidth = 1.0; + + final FragmentProgram program = await FragmentProgram.fromAsset( + 'filter_shader_fractional_texel.frag.iplr', + ); + final FragmentShader shader = program.fragmentShader(); + + final Picture stripePicture = _makePicture((Canvas canvas) { + for (var x = 0; x < width; x++) { + canvas.drawRect( + Rect.fromLTWH(x * stripeWidth, 0, stripeWidth, height.toDouble()), + Paint()..color = x.isEven ? const Color(0xFF000000) : const Color(0xFFFFFFFF), + ); + } + }); + + final Picture transparentPicture = _makePicture((Canvas canvas) { + canvas.drawRect( + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), + Paint()..color = const Color(0x00000000), + ); + }); + + final sceneBuilder = SceneBuilder(); + sceneBuilder.addPicture(Offset.zero, stripePicture); + sceneBuilder.pushBackdropFilter(ImageFilter.shader(shader, filterQuality: filterQuality)); + sceneBuilder.addPicture(Offset.zero, transparentPicture); + sceneBuilder.pop(); + + final Scene scene = sceneBuilder.build(); + final Image image = scene.toImageSync(width, height); + + scene.dispose(); + stripePicture.dispose(); + transparentPicture.dispose(); + shader.dispose(); + + return image; +} + +Future _redAt(Image image, int x, int y) async { + final ByteData data = (await image.toByteData())!; + return data.getUint8((y * image.width + x) * 4); +} From 52ac8930b048c11cb42cbb7fb5520869a9f20604 Mon Sep 17 00:00:00 2001 From: Matt Boetger Date: Thu, 30 Jul 2026 16:19:49 -0700 Subject: [PATCH 130/147] Add java/gradle check when gradle crashes (#189997) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a check for Java/Gradle compatibility after gradle crashes. If the two are incompatible, Gradle can't even start up to run the DependencyVersionChecker.kt - so it throws a cryptic error. Partially address: #189780 Before ``` ❯ flutter config --jdk-dir=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home Setting "jdk-dir" value to "/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home". You may need to restart any open editors for them to read new settings. ❯ flutter build apk WARNING: A restricted method in java.lang.System has been called WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/boetger/.gradle/wrapper/dists/gradle-8.14-all/c2qonpi39x1mddn7hk5gh9iqj/gradle-8.14/lib/native-platform-0.22-milestone-28.jar) WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted methods will be blocked in a future release unless native access is enabled FAILURE: Build failed with an exception. * What went wrong: 25.0.4 * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to get full insights. > Get more help at https://help.gradle.org. BUILD FAILED in 321ms ``` After ``` ❯ flutter config --jdk-dir=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home Setting "jdk-dir" value to "/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home". You may need to restart any open editors for them to read new settings. ❯ flutter build apk Gradle build failed due to Java/Gradle incompatibility. The Java version used for the build is 25.0.4, which is incompatible with Gradle 8.14. To fix this, you can either: 1. Upgrade your project's Gradle version (typically in gradle-wrapper.properties to a version matching the range: compatible Gradle versions for Java 25.0.4 are 9.1.0 to newer). 2. Use a different Java version for Flutter by running `flutter config --jdk-dir=`. Alternatively, you can bypass this check using "--android-skip-build-dependency-validation". ``` ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. --- .../flutter_tools/lib/src/android/gradle.dart | 53 +++ .../lib/src/android/gradle_utils.dart | 37 +- .../android/android_gradle_builder_test.dart | 349 ++++++++++++++++-- 3 files changed, 401 insertions(+), 38 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 1262803b94e91..be7e0313e6a1b 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -25,6 +25,7 @@ import '../base/process.dart'; import '../base/project_migrator.dart'; import '../base/terminal.dart'; import '../base/utils.dart'; +import '../base/version.dart'; import '../build_info.dart'; import '../cache.dart'; import '../convert.dart'; @@ -431,6 +432,56 @@ class AndroidGradleBuilder implements AndroidBuilder { return exitCode; } + // Validate Java and Gradle compatibility after Gradle fails. + // This check is done in Dart after a Gradle crash because: + // 1. If Java and Gradle are incompatible, Gradle can crash during build script + // compilation (e.g. Kotlin DSL compilation failing on newer JDKs) before + // the Flutter Gradle Plugin (DependencyVersionChecker) is even applied. + // See https://github.com/flutter/flutter/issues/189780 for context on + // how JDK upgrades lead to cryptic Gradle compilation crashes. + // 2. Checking only after Gradle crashes avoids blocking builds that currently + // succeed despite an unsupported Java/Gradle version pair. + // 3. This also helps address https://github.com/flutter/flutter/issues/167931 + // by providing actionable version recommendations directly in the error. + Future _checkJavaAndGradleCompatibility(FlutterProject project, BuildInfo buildInfo) async { + if (!buildInfo.androidSkipBuildDependencyValidation) { + final Version? javaVersionObj = _java?.version; + final String? javaVersion = javaVersionObj != null + ? '${javaVersionObj.major}.${javaVersionObj.minor}.${javaVersionObj.patch}' + : null; + final String? gradleVersion = await getGradleVersionFromFile( + project.android.hostAppGradleRoot, + _logger, + ); + if (javaVersion != null && gradleVersion != null) { + if (!gradle.validateJavaAndGradle( + _logger, + javaVersion: javaVersion, + gradleVersion: gradleVersion, + )) { + final JavaGradleCompat? compat = gradle.getValidGradleVersionRangeForJavaVersion( + _logger, + javaV: javaVersion, + ); + final gradleRangeMax = compat != null && compat.gradleMax != null + ? ' to ${compat.gradleMax}' + : ''; + final gradleRangeCompatSuggestion = compat != null + ? '${compat.gradleMin}$gradleRangeMax or newer' + : 'unknown'; + final gradleRangeInfo = + 'compatible Gradle versions for Java $javaVersion are $gradleRangeCompatSuggestion'; + throwToolExit(""" +Gradle build failed due to Java/Gradle incompatibility. +The Java version used for the build is $javaVersion, which is incompatible with Gradle $gradleVersion. +To fix this, you can either: + 1. Upgrade your project's Gradle version (typically in gradle-wrapper.properties to a version matching the range: $gradleRangeInfo). + 2. Use a different Java version for Flutter by running `flutter config --jdk-dir=`."""); + } + } + } + } + /// Builds an app. /// /// * [project] is typically [FlutterProject.current()]. @@ -598,6 +649,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ); if (exitCode != 0) { + await _checkJavaAndGradleCompatibility(project, androidBuildInfo.buildInfo); throwToolExit( 'Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode, @@ -896,6 +948,7 @@ class AndroidGradleBuilder implements AndroidBuilder { if (result.exitCode != 0) { _logger.printStatus(result.stdout, wrap: false); _logger.printError(result.stderr, wrap: false); + await _checkJavaAndGradleCompatibility(project, androidBuildInfo.buildInfo); throwToolExit( 'Gradle task $aarTask failed with exit code ${result.exitCode}.', exitCode: result.exitCode, diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 770e287a4d2f4..d0c2af17a1818 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -331,16 +331,11 @@ String? parseGradleVersionFromDistributionUrl(String? distributionUrl) { return zipParts[1]; } -/// Returns either the gradle-wrapper.properties value from the passed in -/// [directory] or if not present the version available in local path. +/// Returns the gradle-wrapper.properties value from the passed in [directory]. /// -/// If gradle version is not found null is returned. -/// [directory] should be an android directory with a build.gradle file. -Future getGradleVersion( - Directory directory, - Logger logger, - ProcessManager processManager, -) async { +/// If gradle version is not found in the file, null is returned. +/// [directory] should be an android directory. +Future getGradleVersionFromFile(Directory directory, Logger logger) async { final File propertiesFile = getGradleWrapperFile(directory); if (propertiesFile.existsSync()) { @@ -357,14 +352,30 @@ Future getGradleVersion( } } else { // If no distributionUrl log then treat as if there was no propertiesFile. - logger.printTrace( - '$propertiesFile does not provide a Gradle version falling back to system gradle.', - ); + logger.printTrace('$propertiesFile does not provide a Gradle version.'); } } else { // Could not find properties file. - logger.printTrace('$propertiesFile does not exist falling back to system gradle'); + logger.printTrace('$propertiesFile does not exist.'); + } + return null; +} + +/// Returns either the gradle-wrapper.properties value from the passed in +/// [directory] or if not present the version available in local path. +/// +/// If gradle version is not found null is returned. +/// [directory] should be an android directory with a build.gradle file. +Future getGradleVersion( + Directory directory, + Logger logger, + ProcessManager processManager, +) async { + final String? gradleVersion = await getGradleVersionFromFile(directory, logger); + if (gradleVersion != null) { + return gradleVersion; } + logger.printTrace('Falling back to system gradle'); // System installed Gradle version. // TODO(reidbaker): Modify this gradle execution to use gradlew. if (processManager.canRun('gradle')) { diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index af265f170e8d7..7c4cd490c7faa 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -19,6 +19,7 @@ import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/user_messages.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/globals.dart' as globals; @@ -1328,11 +1329,7 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - CpuArch.arm64, - CpuArch.armv7, - CpuArch.x64, - ], + targetArchs: [CpuArch.arm64, CpuArch.armv7, CpuArch.x64], ), target: 'lib/main.dart', isBuildingBundle: true, @@ -1403,11 +1400,7 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - CpuArch.arm64, - CpuArch.armv7, - CpuArch.x64, - ], + targetArchs: [CpuArch.arm64, CpuArch.armv7, CpuArch.x64], ), target: 'lib/main.dart', isBuildingBundle: true, @@ -1471,11 +1464,7 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - CpuArch.arm64, - CpuArch.armv7, - CpuArch.x64, - ], + targetArchs: [CpuArch.arm64, CpuArch.armv7, CpuArch.x64], ), target: 'lib/main.dart', isBuildingBundle: true, @@ -1561,11 +1550,7 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - CpuArch.arm64, - CpuArch.armv7, - CpuArch.x64, - ], + targetArchs: [CpuArch.arm64, CpuArch.armv7, CpuArch.x64], ), target: 'lib/main.dart', isBuildingBundle: true, @@ -1654,11 +1639,7 @@ void main() { treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', ), - targetArchs: [ - CpuArch.arm64, - CpuArch.armv7, - CpuArch.x64, - ], + targetArchs: [CpuArch.arm64, CpuArch.armv7, CpuArch.x64], ), target: 'lib/main.dart', isBuildingBundle: true, @@ -3069,6 +3050,324 @@ Gradle Crashed ); expect(processManager, hasNoRemainingExpectations); }, overrides: {AndroidStudio: () => FakeAndroidStudio()}); + + testUsingContext( + 'build apk throws ToolExit when Java and Gradle versions are incompatible', + () async { + final builder = AndroidGradleBuilder( + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + analytics: fakeAnalytics, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + androidSdk: globals.androidSdk, + ); + + // Setup incompatible gradle version (e.g. 8.0 for Java 21) in gradle-wrapper.properties + fileSystem.file('android/gradle/wrapper/gradle-wrapper.properties') + ..createSync(recursive: true) + ..writeAsStringSync( + r'distributionUrl=https://services.gradle.org/distributions/gradle-8.0-all.zip', + ); + + fileSystem.file('android/gradlew').createSync(recursive: true); + fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); + fileSystem.file('android/build.gradle').createSync(recursive: true); + fileSystem.directory('android').childDirectory('app').childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + + final FlutterProject project = FlutterProject.fromDirectoryTest( + fileSystem.currentDirectory, + ); + project.android.appManifestFile + ..createSync(recursive: true) + ..writeAsStringSync(minimalV2EmbeddingManifest); + + processManager.addCommand( + FakeCommand( + command: [ + 'gradlew', + '-q', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + '-Ptarget=lib/main.dart', + '-Pbase-application-name=android.app.Application', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Pflutter.androidSdkRoot=${sdkPath()}', + '-Pflutter.installedNdkVersions=29.0.13846066', + '-Pflutter.sdkManagerPath=${sdkManagerPath()}', + 'assembleDevRelease', + ], + exitCode: 1, + ), + ); + + await expectLater( + () => builder.buildGradleApp( + project: project, + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + 'dev', + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + configOnly: false, + localGradleErrors: const [], + ), + throwsToolExit( + message: + 'Gradle build failed due to Java/Gradle incompatibility.\n' + 'The Java version used for the build is 21.0.0, which is incompatible with Gradle 8.0.\n' + 'To fix this, you can either:\n' + " 1. Upgrade your project's Gradle version (typically in gradle-wrapper.properties to a version matching the range: compatible Gradle versions for Java 21.0.0 are 8.4 or newer).\n" + ' 2. Use a different Java version for Flutter by running `flutter config --jdk-dir=`.' + ), + ); + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + AndroidSdk: () { + fileSystem.directory(sdkPath()).createSync(recursive: true); + fileSystem.directory(sdkLicensesPath()).createSync(recursive: true); + fileSystem + .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin')) + .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager') + .createSync(recursive: true); + fileSystem + .directory(ndkPath('29.0.13846066')) + .childFile('source.properties') + .createSync(recursive: true); + fileSystem.directory(ndkPath('29.0.13846066-bad')).createSync(recursive: true); + return AndroidSdk( + fileSystem.directory(sdkPath()), + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + fileSystem: fileSystem, + ); + }, + AndroidStudio: () => FakeAndroidStudio(), + }, + ); + + testUsingContext( + 'build apk succeeds when Java and Gradle versions are incompatible but Gradle build succeeds', + () async { + final builder = AndroidGradleBuilder( + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + analytics: fakeAnalytics, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + androidSdk: globals.androidSdk, + ); + + // Setup incompatible gradle version (e.g. 8.0 for Java 21) in gradle-wrapper.properties + fileSystem.file('android/gradle/wrapper/gradle-wrapper.properties') + ..createSync(recursive: true) + ..writeAsStringSync( + r'distributionUrl=https://services.gradle.org/distributions/gradle-8.0-all.zip', + ); + + fileSystem.file('android/gradlew').createSync(recursive: true); + fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); + fileSystem.file('android/build.gradle').createSync(recursive: true); + fileSystem.directory('android').childDirectory('app').childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + fileSystem + .directory('build') + .childDirectory('app') + .childDirectory('outputs') + .childDirectory('flutter-apk') + .childFile('app-dev-release.apk') + .createSync(recursive: true); + + final FlutterProject project = FlutterProject.fromDirectoryTest( + fileSystem.currentDirectory, + ); + project.android.appManifestFile + ..createSync(recursive: true) + ..writeAsStringSync(minimalV2EmbeddingManifest); + + processManager.addCommand( + FakeCommand( + command: [ + 'gradlew', + '-q', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + '-Ptarget=lib/main.dart', + '-Pbase-application-name=android.app.Application', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Pflutter.androidSdkRoot=${sdkPath()}', + '-Pflutter.installedNdkVersions=29.0.13846066', + '-Pflutter.sdkManagerPath=${sdkManagerPath()}', + 'assembleDevRelease', + ], + ), + ); + + await builder.buildGradleApp( + project: project, + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + 'dev', + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + configOnly: false, + localGradleErrors: const [], + ); + + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + AndroidSdk: () { + fileSystem.directory(sdkPath()).createSync(recursive: true); + fileSystem.directory(sdkLicensesPath()).createSync(recursive: true); + fileSystem + .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin')) + .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager') + .createSync(recursive: true); + fileSystem + .directory(ndkPath('29.0.13846066')) + .childFile('source.properties') + .createSync(recursive: true); + fileSystem.directory(ndkPath('29.0.13846066-bad')).createSync(recursive: true); + return AndroidSdk( + fileSystem.directory(sdkPath()), + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + fileSystem: fileSystem, + ); + }, + AndroidStudio: () => FakeAndroidStudio(), + }, + ); + + testUsingContext( + 'skips Java and Gradle compatibility check when androidSkipBuildDependencyValidation is true', + () async { + final builder = AndroidGradleBuilder( + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + analytics: fakeAnalytics, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + androidSdk: globals.androidSdk, + ); + + // Setup incompatible gradle version (e.g. 8.0 for Java 21) in gradle-wrapper.properties + // This would normally fail unless skipped. + fileSystem.file('android/gradle/wrapper/gradle-wrapper.properties') + ..createSync(recursive: true) + ..writeAsStringSync( + r'distributionUrl=https://services.gradle.org/distributions/gradle-8.0-all.zip', + ); + + fileSystem.file('android/gradlew').createSync(recursive: true); + fileSystem.directory('android').childFile('gradle.properties').createSync(recursive: true); + fileSystem.file('android/build.gradle').createSync(recursive: true); + fileSystem.directory('android').childDirectory('app').childFile('build.gradle') + ..createSync(recursive: true) + ..writeAsStringSync('apply from: irrelevant/flutter.gradle'); + fileSystem + .directory('build') + .childDirectory('app') + .childDirectory('outputs') + .childDirectory('flutter-apk') + .childFile('app-dev-release.apk') + .createSync(recursive: true); + + final FlutterProject project = FlutterProject.fromDirectoryTest( + fileSystem.currentDirectory, + ); + project.android.appManifestFile + ..createSync(recursive: true) + ..writeAsStringSync(minimalV2EmbeddingManifest); + + processManager.addCommand( + FakeCommand( + command: [ + 'gradlew', + '-q', + '-PskipDependencyChecks=true', + '-Ptarget-platform=android-arm,android-arm64,android-x64', + '-Ptarget=lib/main.dart', + '-Pbase-application-name=android.app.Application', + '-Pdart-obfuscation=false', + '-Ptrack-widget-creation=false', + '-Ptree-shake-icons=false', + '-Pflutter.androidSdkRoot=${sdkPath()}', + '-Pflutter.installedNdkVersions=29.0.13846066', + '-Pflutter.sdkManagerPath=${sdkManagerPath()}', + 'assembleDevRelease', + ], + ), + ); + + await builder.buildGradleApp( + project: project, + androidBuildInfo: const AndroidBuildInfo( + BuildInfo( + BuildMode.release, + 'dev', + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + androidSkipBuildDependencyValidation: true, // Skip validation + ), + ), + target: 'lib/main.dart', + isBuildingBundle: false, + configOnly: false, + localGradleErrors: const [], + ); + + expect(processManager, hasNoRemainingExpectations); + }, + overrides: { + AndroidSdk: () { + fileSystem.directory(sdkPath()).createSync(recursive: true); + fileSystem.directory(sdkLicensesPath()).createSync(recursive: true); + fileSystem + .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin')) + .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager') + .createSync(recursive: true); + fileSystem + .directory(ndkPath('29.0.13846066')) + .childFile('source.properties') + .createSync(recursive: true); + fileSystem.directory(ndkPath('29.0.13846066-bad')).createSync(recursive: true); + return AndroidSdk( + fileSystem.directory(sdkPath()), + java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')), + fileSystem: fileSystem, + ); + }, + AndroidStudio: () => FakeAndroidStudio(), + }, + ); }); } From 3ca38538ff9ee71ea1ccf35b0b67ff33667faf75 Mon Sep 17 00:00:00 2001 From: Muhammad Kamel Date: Fri, 31 Jul 2026 02:21:08 +0300 Subject: [PATCH 131/147] fix: text selection two handles directionality. (#179928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #187799 This PR fixes an issue where text selection handles would render facing the wrong direction when the text directionality differed from the ambient directionality (e.g., selecting English (LTR) text within an application set to Arabic (RTL)), and also when a single selection spans both LTR and RTL runs. Previously, `TextSelectionOverlay` determined the start and end handle types based primarily on the ambient `TextDirection` and the logical selection range. This caused handles to swap (pointing away from the selection) in mixed-directionality scenarios. This change updates the logic in `_updateSelectionOverlay` to derive each handle's type from the **per-endpoint `TextDirection`** reported by `TextSelectionPoint.direction`, falling back to the render object's `textDirection` when an endpoint does not provide one: * Start handle: `TextDirection.ltr` → `TextSelectionHandleType.left`, `TextDirection.rtl` → `TextSelectionHandleType.right`. * End handle: `TextDirection.ltr` → `TextSelectionHandleType.right`, `TextDirection.rtl` → `TextSelectionHandleType.left`. Because each endpoint is evaluated independently, selections that cross a directionality boundary (e.g. `"abc مرحبا"`) get handle types that match the local direction at each end, so both handles continue to "hug" the selection. On iOS, UIKit conventionally keeps selection handles aligned with the field's own direction rather than the per-endpoint direction. To preserve platform behavior, when `defaultTargetPlatform == TargetPlatform.iOS` both handles use the render object's `textDirection` instead of the endpoint directions. When a non-collapsed selection yields fewer than two endpoints (e.g. the selection is scrolled or clipped out of view), handle direction falls back to the field's `textDirection` instead of asserting. Collapsed selections continue to use `TextSelectionHandleType.collapsed` for both handles and are unaffected by this change. **Tests added** Added new tests in `text_selection_test.dart`: Android (`TargetPlatformVariant.only(TargetPlatform.android)`), driven by a table of `_DirectionalityTestCase`s that assert the endpoint directions reported by `RenderEditable` and the handle types received by a spy `TextSelectionControls`: * ambient LTR, English text * ambient RTL, English text * ambient RTL, Arabic text * ambient LTR, Arabic text * ambient LTR, mixed `"abc مرحبا"` (English then Arabic) * ambient RTL, mixed `"abc مرحبا"` (English then Arabic) iOS (`TargetPlatformVariant.only(TargetPlatform.iOS)`): * Verifies that for the mixed `"abc مرحبا"` selection under ambient LTR, both handles follow the field's `textDirection` (i.e. `left` / `right`) rather than the per-endpoint directions — exercising the UIKit-compatibility branch. The new directionality tests set `selectAllOnFocus: false` explicitly (with an inline comment) because web CI defaults `EditableText.selectAllOnFocus` to `true`, which can select all text on focus and prevent handle rebuilds in these tests. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. ![image_issue](https://github.com/user-attachments/assets/7de61b60-c1dd-44bb-a5c8-34ff5d451350) [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/widgets/text_selection.dart | 73 ++++-- .../test/widgets/editable_text_tester.dart | 7 + .../test/widgets/text_selection_test.dart | 240 ++++++++++++++++++ 3 files changed, 294 insertions(+), 26 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 05f696b37ce29..8daf1d9e54fc4 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -548,22 +548,58 @@ class TextSelectionOverlay { } void _updateSelectionOverlay() { + final List endpoints = renderObject.getEndpointsForSelection(_selection); + assert(endpoints.isNotEmpty); + + final TextSelectionHandleType startHandleType; + final TextSelectionHandleType endHandleType; + if (_selection.isCollapsed) { + startHandleType = TextSelectionHandleType.collapsed; + endHandleType = TextSelectionHandleType.collapsed; + } else { + final TextDirection textDirection = renderObject.textDirection; + // UIKit keeps selection handles aligned with the field direction. + final preferRenderObjectDirectionForSelectionHandles = + defaultTargetPlatform == TargetPlatform.iOS; + final TextDirection startHandleDirection; + final TextDirection endHandleDirection; + // A non-collapsed selection might return fewer than two endpoints if the + // text layout lacks boxes for the selected range. This typically happens when: + // + // * Render lag: The overlay updated with a new editing value before the + // render object laid out the new text (selection offsets are out of bounds). + // * Split graphemes: A selection boundary falls inside a multi-code-unit + // cluster (like an emoji or combining character). + // * Degenerate layout: The layout is temporarily squashed (e.g., + // preferredLineHeight is 0 during a fold transition). + // + // In these cases, we fall back to the field's textDirection. + if (preferRenderObjectDirectionForSelectionHandles || endpoints.length < 2) { + startHandleDirection = textDirection; + endHandleDirection = textDirection; + } else { + startHandleDirection = endpoints.first.direction ?? textDirection; + endHandleDirection = endpoints.last.direction ?? textDirection; + } + + startHandleType = switch (startHandleDirection) { + TextDirection.ltr => TextSelectionHandleType.left, + TextDirection.rtl => TextSelectionHandleType.right, + }; + endHandleType = switch (endHandleDirection) { + TextDirection.ltr => TextSelectionHandleType.right, + TextDirection.rtl => TextSelectionHandleType.left, + }; + } + _selectionOverlay // Update selection handle metrics. - ..startHandleType = _chooseType( - renderObject.textDirection, - TextSelectionHandleType.left, - TextSelectionHandleType.right, - ) + ..startHandleType = startHandleType ..lineHeightAtStart = _getStartGlyphHeight() - ..endHandleType = _chooseType( - renderObject.textDirection, - TextSelectionHandleType.right, - TextSelectionHandleType.left, - ) + ..endHandleType = endHandleType ..lineHeightAtEnd = _getEndGlyphHeight() // Update selection toolbar metrics. - ..selectionEndpoints = renderObject.getEndpointsForSelection(_selection) + ..selectionEndpoints = endpoints ..toolbarLocation = renderObject.lastSecondaryTapDownPosition; } @@ -1046,21 +1082,6 @@ class TextSelectionOverlay { SelectionChangedCause.drag, ); } - - TextSelectionHandleType _chooseType( - TextDirection textDirection, - TextSelectionHandleType ltrType, - TextSelectionHandleType rtlType, - ) { - if (_selection.isCollapsed) { - return TextSelectionHandleType.collapsed; - } - - return switch (textDirection) { - TextDirection.ltr => ltrType, - TextDirection.rtl => rtlType, - }; - } } /// An object that manages a pair of selection handles and a toolbar. diff --git a/packages/flutter/test/widgets/editable_text_tester.dart b/packages/flutter/test/widgets/editable_text_tester.dart index c631f4b240d41..61c41298bd185 100644 --- a/packages/flutter/test/widgets/editable_text_tester.dart +++ b/packages/flutter/test/widgets/editable_text_tester.dart @@ -41,6 +41,7 @@ class TestTextField extends StatefulWidget { this.controller, this.onSubmitted, this.showSelectionHandles = false, + this.selectAllOnFocus, }); final Iterable? autofillHints; @@ -60,6 +61,11 @@ class TestTextField extends StatefulWidget { final ValueChanged? onSubmitted; final bool showSelectionHandles; + /// Controls whether all text is selected when the field receives focus. + /// + /// When null, [EditableText] platform defaults apply. + final bool? selectAllOnFocus; + @override State createState() => _TestTextFieldState(); } @@ -127,6 +133,7 @@ class _TestTextFieldState extends State style: widget.style ?? const TextStyle(), // required by editable text. controller: _effectiveController, // required by editable text. showSelectionHandles: widget.showSelectionHandles, + selectAllOnFocus: widget.selectAllOnFocus, ), ), ); diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 193398bf24d69..4c33ff6fadd05 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -2087,6 +2087,179 @@ void main() { await tester.pump(); expect(find.byType(Placeholder), findsOneWidget); }, skip: kIsWeb); // [intended] On web, we use native context menus for text fields. + + const androidDirectionalityTestCases = <_DirectionalityTestCase>[ + _DirectionalityTestCase( + description: 'ambient LTR, English text', + ambientDirection: TextDirection.ltr, + text: 'Hello World', + selectionBase: 0, + selectionExtent: 5, + expectedStartEndpointDirection: TextDirection.ltr, + expectedEndEndpointDirection: TextDirection.ltr, + expectedStartHandleType: TextSelectionHandleType.left, + expectedEndHandleType: TextSelectionHandleType.right, + ), + _DirectionalityTestCase( + description: 'ambient RTL, English text', + ambientDirection: TextDirection.rtl, + text: 'Hello World', + selectionBase: 0, + selectionExtent: 5, + expectedStartEndpointDirection: TextDirection.ltr, + expectedEndEndpointDirection: TextDirection.ltr, + expectedStartHandleType: TextSelectionHandleType.left, + expectedEndHandleType: TextSelectionHandleType.right, + ), + _DirectionalityTestCase( + description: 'ambient RTL, Arabic text', + ambientDirection: TextDirection.rtl, + text: 'مرحبا بالعالم', + selectionBase: 0, + selectionExtent: 5, + expectedStartEndpointDirection: TextDirection.rtl, + expectedEndEndpointDirection: TextDirection.rtl, + expectedStartHandleType: TextSelectionHandleType.right, + expectedEndHandleType: TextSelectionHandleType.left, + ), + _DirectionalityTestCase( + description: 'ambient LTR, Arabic text', + ambientDirection: TextDirection.ltr, + text: 'مرحبا بالعالم', + selectionBase: 0, + selectionExtent: 5, + expectedStartEndpointDirection: TextDirection.rtl, + expectedEndEndpointDirection: TextDirection.rtl, + expectedStartHandleType: TextSelectionHandleType.right, + expectedEndHandleType: TextSelectionHandleType.left, + ), + _DirectionalityTestCase( + description: 'ambient LTR, English then Arabic text', + ambientDirection: TextDirection.ltr, + text: 'abc مرحبا', + selectionBase: 0, + selectionExtent: 9, + expectedStartEndpointDirection: TextDirection.ltr, + expectedEndEndpointDirection: TextDirection.rtl, + expectedStartHandleType: TextSelectionHandleType.left, + expectedEndHandleType: TextSelectionHandleType.left, + ), + _DirectionalityTestCase( + description: 'ambient RTL, English then Arabic text', + ambientDirection: TextDirection.rtl, + text: 'abc مرحبا', + selectionBase: 0, + selectionExtent: 9, + expectedStartEndpointDirection: TextDirection.rtl, + expectedEndEndpointDirection: TextDirection.ltr, + expectedStartHandleType: TextSelectionHandleType.right, + expectedEndHandleType: TextSelectionHandleType.right, + ), + ]; + + for (final testCase in androidDirectionalityTestCases) { + testWidgets( + 'Android selection handles match endpoint direction: ${testCase.description}', + (WidgetTester tester) async { + final customControls = _DirectionalitySpyTextSelectionControls(); + final controller = TextEditingController(text: testCase.text); + final focusNode = FocusNode(); + addTearDown(controller.dispose); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: Directionality( + textDirection: testCase.ambientDirection, + child: TestTextField( + controller: controller, + focusNode: focusNode, + selectionControls: customControls, + // On the web selectAllOnFocus defaults to true, interfering with + // this test's programmatic selection. + selectAllOnFocus: false, + ), + ), + ), + ); + + final RenderEditable renderEditable = tester.allRenderObjects + .whereType() + .first; + expect(renderEditable.textDirection, testCase.ambientDirection); + + focusNode.requestFocus(); + await tester.pump(); + + customControls.clearBuiltHandleTypes(); + controller.selection = TextSelection( + baseOffset: testCase.selectionBase, + extentOffset: testCase.selectionExtent, + ); + await tester.pumpAndSettle(); + + final List endpoints = renderEditable.getEndpointsForSelection( + controller.selection, + ); + expect(endpoints, hasLength(2)); + expect(endpoints.first.direction, testCase.expectedStartEndpointDirection); + expect(endpoints.last.direction, testCase.expectedEndEndpointDirection); + expect(customControls.builtHandleTypes, hasLength(2)); + expect(customControls.builtHandleTypes.first, testCase.expectedStartHandleType); + expect(customControls.builtHandleTypes.last, testCase.expectedEndHandleType); + }, + variant: TargetPlatformVariant.only(TargetPlatform.android), + ); + } + + testWidgets( + 'selection handles use text direction for mixed-directionality text on iOS', + (WidgetTester tester) async { + final customControls = _DirectionalitySpyTextSelectionControls(); + final controller = TextEditingController(text: 'abc مرحبا'); + addTearDown(controller.dispose); + final focusNode = FocusNode(); + addTearDown(focusNode.dispose); + + await tester.pumpWidget( + TestWidgetsApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: TestTextField( + controller: controller, + focusNode: focusNode, + selectionControls: customControls, + // On the web selectAllOnFocus defaults to true, interfering with + // this test's programmatic selection. + selectAllOnFocus: false, + ), + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + + customControls.clearBuiltHandleTypes(); + controller.selection = TextSelection(baseOffset: 0, extentOffset: controller.text.length); + await tester.pumpAndSettle(); + + final RenderEditable renderEditable = tester.allRenderObjects + .whereType() + .first; + final List endpoints = renderEditable.getEndpointsForSelection( + controller.selection, + ); + + expect(endpoints, hasLength(2)); + expect(endpoints.first.direction, TextDirection.ltr); + expect(endpoints.last.direction, TextDirection.rtl); + expect(customControls.builtHandleTypes, hasLength(2)); + expect(customControls.builtHandleTypes.first, TextSelectionHandleType.left); + expect(customControls.builtHandleTypes.last, TextSelectionHandleType.right); + }, + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); } class FakeTextSelectionGestureDetectorBuilderDelegate @@ -2369,3 +2542,70 @@ class _MockTextSelectionHandleControls extends TextSelectionControls ); } } + +class _DirectionalitySpyTextSelectionControls extends TextSelectionControls { + final List builtHandleTypes = []; + + void clearBuiltHandleTypes() { + builtHandleTypes.clear(); + } + + @override + Widget buildHandle( + BuildContext context, + TextSelectionHandleType type, + double textLineHeight, [ + VoidCallback? onTap, + ]) { + builtHandleTypes.add(type); + return SizedBox.square(dimension: textLineHeight); + } + + @override + Widget buildToolbar( + BuildContext context, + Rect globalEditableRegion, + double textLineHeight, + Offset selectionMidpoint, + List endpoints, + TextSelectionDelegate delegate, + ValueListenable? clipboardStatus, + Offset? lastSecondaryTapDownPosition, + ) { + return const SizedBox.shrink(); + } + + @override + Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { + return Offset.zero; + } + + @override + Size getHandleSize(double textLineHeight) { + return Size.square(textLineHeight); + } +} + +class _DirectionalityTestCase { + const _DirectionalityTestCase({ + required this.description, + required this.ambientDirection, + required this.text, + required this.selectionBase, + required this.selectionExtent, + required this.expectedStartEndpointDirection, + required this.expectedEndEndpointDirection, + required this.expectedStartHandleType, + required this.expectedEndHandleType, + }); + + final String description; + final TextDirection ambientDirection; + final String text; + final int selectionBase; + final int selectionExtent; + final TextDirection expectedStartEndpointDirection; + final TextDirection expectedEndEndpointDirection; + final TextSelectionHandleType expectedStartHandleType; + final TextSelectionHandleType expectedEndHandleType; +} From cde15321aa0b265caeae2231c762002010661f3f Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 19:52:16 -0400 Subject: [PATCH 132/147] Roll Dart SDK from 43f7de5f4977 to 96f49fc43f59 (4 revisions) (#190312) https://dart.googlesource.com/sdk.git/+log/43f7de5f4977..96f49fc43f59 2026-07-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-80.0.dev 2026-07-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-79.0.dev 2026-07-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-78.0.dev 2026-07-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-77.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC aaclarke@google.com,dart-vm-team@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index e1003bba5ab36..6381e8e5ac01c 100644 --- a/DEPS +++ b/DEPS @@ -56,14 +56,14 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '43f7de5f49777afc803a8fe69a752d3550f0602f', + 'dart_revision': '96f49fc43f592fbaa41dc451f7896653385ac1c9', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': '9926156a583cec3d22d521232b31c70fa9a87dc1', 'dart_boringssl_rev': '22a0079b189c391b95689813a41982ce11876f0a', - 'dart_core_rev': 'be0b1531c445a185d3e93887b8d0355fc766c314', - 'dart_devtools_rev': '12d595649f189f1896722623f72599077f476848', + 'dart_core_rev': '25926e846003a123e5249df42c5a1ce30c246e3a', + 'dart_devtools_rev': '21f1838f3a9b138ac377efb953ca5a53c8832e75', 'dart_ecosystem_rev': '848b3bf3b757d2e9ae4d60030eeed5756c87783f', 'dart_http_rev': '5d94ef52582867e077bf41c3fa20fb8b1d1d834e', 'dart_i18n_rev': 'd0683bdea253d19a4350f5bc2be9017aba61837f', @@ -330,7 +330,7 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git' + '@' + Var('dart_binaryen_rev'), 'engine/src/flutter/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:12d595649f189f1896722623f72599077f476848'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:21f1838f3a9b138ac377efb953ca5a53c8832e75'}]}, 'engine/src/flutter/third_party/dart/third_party/perfetto/src': Var('chromium_git') + '/external/github.com/google/perfetto' + '@' + Var('dart_perfetto_rev'), From a111cd0fa9c6912521546f1d606450324e37349a Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 31 Jul 2026 09:03:38 +0900 Subject: [PATCH 133/147] Support reduceMotion accessibility feature in MediaQuery (#190287) Exposes the platform's "Reduce Motion" accessibility setting through MediaQueryData, so widgets can react to it with proper dependency tracking instead of reading it ad hoc off the platform dispatcher. The reduceMotion flag tracking the iOS "Reduce Motion" setting already exists at the engine layer as `dart:ui.AccessibilityFeatures.reduceMotion`, but it was never surfaced on MediaQueryData. This mirrors the existing disableAnimations support, which corresponds to Android's "Remove animations" setting. Unlike disableAnimations, reduceMotion is not consulted by framework-level animation APIs such as AnimationController. It is intended for widgets that want to tone down or replace non-essential motion, for example, by substituting a cross-fade for a slide. No engine or SemanticsBinding changes are required: as with disableAnimations, MediaQueryData reads reduceMotion directly from platformDispatcher.accessibilityFeatures, and FakeAccessibilityFeatures already supports the flag. Issue: https://github.com/flutter/flutter/issues/4827 Issue: https://github.com/flutter/flutter/issues/65874 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/widgets/media_query.dart | 59 +++++++++++++++++++ .../test/widgets/media_query_test.dart | 33 +++++++++++ 2 files changed, 92 insertions(+) diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index 03ed8f4f5bb02..47392bd517dc1 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -101,6 +101,9 @@ enum _MediaQueryAspect { /// Specifies the aspect corresponding to [MediaQueryData.disableAnimations]. disableAnimations, + /// Specifies the aspect corresponding to [MediaQueryData.reduceMotion]. + reduceMotion, + /// Specifies the aspect corresponding to [MediaQueryData.boldText]. boldText, @@ -228,6 +231,7 @@ class MediaQueryData { this.highContrast = false, this.onOffSwitchLabels = false, this.disableAnimations = false, + this.reduceMotion = false, this.boldText = false, this.supportsAnnounce = false, this.navigationMode = NavigationMode.traditional, @@ -319,6 +323,8 @@ class MediaQueryData { disableAnimations = platformData?.disableAnimations ?? view.platformDispatcher.accessibilityFeatures.disableAnimations, + reduceMotion = + platformData?.reduceMotion ?? view.platformDispatcher.accessibilityFeatures.reduceMotion, boldText = platformData?.boldText ?? view.platformDispatcher.accessibilityFeatures.boldText, supportsAnnounce = platformData?.supportsAnnounce ?? @@ -692,6 +698,29 @@ class MediaQueryData { /// originates. final bool disableAnimations; + /// Whether the platform is requesting that animations be reduced or replaced + /// with cross-fades in preference to motion effects. + /// + /// This corresponds to the iOS "Reduce Motion" accessibility setting. + /// + /// Unlike [disableAnimations], this flag does not automatically alter + /// framework animations such as those controlled via [AnimationController]. + /// Instead, it is intended to be read by widgets that want to tone down or + /// replace non-essential motion, for example by substituting a cross-fade + /// for a slide transition. + /// + /// When implementing custom animations, you should check this property and + /// adjust behavior accordingly; for example, by preferring a fade over + /// movement when it is true. + /// + /// See also: + /// + /// * [dart:ui.AccessibilityFeatures.reduceMotion], the underlying primitive + /// flag provided by the platform. + /// * [dart:ui.PlatformDispatcher.accessibilityFeatures], where the setting + /// originates. + final bool reduceMotion; + /// Whether the platform is requesting that text be drawn with a bold font /// weight. /// @@ -852,6 +881,7 @@ class MediaQueryData { bool? highContrast, bool? onOffSwitchLabels, bool? disableAnimations, + bool? reduceMotion, bool? invertColors, bool? accessibleNavigation, bool? boldText, @@ -879,6 +909,7 @@ class MediaQueryData { highContrast: highContrast ?? this.highContrast, onOffSwitchLabels: onOffSwitchLabels ?? this.onOffSwitchLabels, disableAnimations: disableAnimations ?? this.disableAnimations, + reduceMotion: reduceMotion ?? this.reduceMotion, accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation, boldText: boldText ?? this.boldText, supportsAnnounce: supportsAnnounce ?? this.supportsAnnounce, @@ -927,6 +958,7 @@ class MediaQueryData { highContrast: highContrast, onOffSwitchLabels: onOffSwitchLabels, disableAnimations: disableAnimations, + reduceMotion: reduceMotion, accessibleNavigation: accessibleNavigation, boldText: boldText, supportsAnnounce: supportsAnnounce, @@ -962,6 +994,7 @@ class MediaQueryData { highContrast: highContrast, onOffSwitchLabels: onOffSwitchLabels, disableAnimations: disableAnimations, + reduceMotion: reduceMotion, accessibleNavigation: accessibleNavigation, boldText: boldText, supportsAnnounce: supportsAnnounce, @@ -1165,6 +1198,7 @@ class MediaQueryData { other.highContrast == highContrast && other.onOffSwitchLabels == onOffSwitchLabels && other.disableAnimations == disableAnimations && + other.reduceMotion == reduceMotion && other.invertColors == invertColors && other.accessibleNavigation == accessibleNavigation && other.boldText == boldText && @@ -1193,6 +1227,7 @@ class MediaQueryData { highContrast, onOffSwitchLabels, disableAnimations, + reduceMotion, invertColors, accessibleNavigation, boldText, @@ -1225,6 +1260,7 @@ class MediaQueryData { 'highContrast: $highContrast', 'onOffSwitchLabels: $onOffSwitchLabels', 'disableAnimations: $disableAnimations', + 'reduceMotion: $reduceMotion', 'invertColors: $invertColors', 'boldText: $boldText', 'navigationMode: ${navigationMode.name}', @@ -2026,6 +2062,28 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> { static bool? maybeDisableAnimationsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.disableAnimations)?.disableAnimations; + /// Returns [MediaQueryData.reduceMotion] for the nearest [MediaQuery] + /// ancestor or false, if no such ancestor exists. + /// + /// Use of this method will cause the given [context] to rebuild any time that + /// the [MediaQueryData.reduceMotion] property of the ancestor + /// [MediaQuery] changes. + /// + /// {@macro flutter.widgets.media_query.MediaQuery.dontUseOf} + static bool reduceMotionOf(BuildContext context) => + _of(context, _MediaQueryAspect.reduceMotion).reduceMotion; + + /// Returns [MediaQueryData.reduceMotion] for the nearest [MediaQuery] + /// ancestor or null, if no such ancestor exists. + /// + /// Use of this method will cause the given [context] to rebuild any time that + /// the [MediaQueryData.reduceMotion] property of the ancestor + /// [MediaQuery] changes. + /// + /// {@macro flutter.widgets.media_query.MediaQuery.dontUseMaybeOf} + static bool? maybeReduceMotionOf(BuildContext context) => + _maybeOf(context, _MediaQueryAspect.reduceMotion)?.reduceMotion; + /// Returns the [MediaQueryData.boldText] accessibility setting for the /// nearest [MediaQuery] ancestor or false, if no such ancestor exists. /// @@ -2273,6 +2331,7 @@ class MediaQuery extends InheritedModel<_MediaQueryAspect> { data.onOffSwitchLabels != oldWidget.data.onOffSwitchLabels, _MediaQueryAspect.disableAnimations => data.disableAnimations != oldWidget.data.disableAnimations, + _MediaQueryAspect.reduceMotion => data.reduceMotion != oldWidget.data.reduceMotion, _MediaQueryAspect.boldText => data.boldText != oldWidget.data.boldText, _MediaQueryAspect.supportsAnnounce => data.supportsAnnounce != oldWidget.data.supportsAnnounce, diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 97f9c0a06f085..355539e526765 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -155,6 +155,7 @@ void main() { expect(data.accessibleNavigation, false); expect(data.invertColors, false); expect(data.disableAnimations, false); + expect(data.reduceMotion, false); expect(data.boldText, false); expect(data.highContrast, false); expect(data.onOffSwitchLabels, false); @@ -172,6 +173,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -206,6 +208,7 @@ void main() { expect(data.accessibleNavigation, platformData.accessibleNavigation); expect(data.invertColors, platformData.invertColors); expect(data.disableAnimations, platformData.disableAnimations); + expect(data.reduceMotion, platformData.reduceMotion); expect(data.boldText, platformData.boldText); expect(data.highContrast, platformData.highContrast); expect(data.onOffSwitchLabels, platformData.onOffSwitchLabels); @@ -257,6 +260,7 @@ void main() { data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations, ); + expect(data.reduceMotion, tester.platformDispatcher.accessibilityFeatures.reduceMotion); expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); expect( @@ -283,6 +287,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -331,6 +336,7 @@ void main() { expect(data.accessibleNavigation, platformData.accessibleNavigation); expect(data.invertColors, platformData.invertColors); expect(data.disableAnimations, platformData.disableAnimations); + expect(data.reduceMotion, platformData.reduceMotion); expect(data.boldText, platformData.boldText); expect(data.highContrast, platformData.highContrast); expect(data.onOffSwitchLabels, platformData.onOffSwitchLabels); @@ -401,6 +407,7 @@ void main() { data.disableAnimations, tester.platformDispatcher.accessibilityFeatures.disableAnimations, ); + expect(data.reduceMotion, tester.platformDispatcher.accessibilityFeatures.reduceMotion); expect(data.boldText, tester.platformDispatcher.accessibilityFeatures.boldText); expect(data.highContrast, tester.platformDispatcher.accessibilityFeatures.highContrast); expect( @@ -592,6 +599,7 @@ void main() { expect(copied.accessibleNavigation, data.accessibleNavigation); expect(copied.invertColors, data.invertColors); expect(copied.disableAnimations, data.disableAnimations); + expect(copied.reduceMotion, data.reduceMotion); expect(copied.boldText, data.boldText); expect(copied.highContrast, data.highContrast); expect(copied.onOffSwitchLabels, data.onOffSwitchLabels); @@ -634,6 +642,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -654,6 +663,7 @@ void main() { expect(copied.accessibleNavigation, true); expect(copied.invertColors, true); expect(copied.disableAnimations, true); + expect(copied.reduceMotion, true); expect(copied.boldText, true); expect(copied.highContrast, true); expect(copied.onOffSwitchLabels, true); @@ -703,6 +713,7 @@ void main() { expect(updatedData.accessibleNavigation, data.accessibleNavigation); expect(updatedData.invertColors, data.invertColors); expect(updatedData.disableAnimations, data.disableAnimations); + expect(updatedData.reduceMotion, data.reduceMotion); expect(updatedData.boldText, data.boldText); expect(updatedData.highContrast, data.highContrast); expect(updatedData.onOffSwitchLabels, data.onOffSwitchLabels); @@ -747,6 +758,7 @@ void main() { expect(updatedData.accessibleNavigation, data.accessibleNavigation); expect(updatedData.invertColors, data.invertColors); expect(updatedData.disableAnimations, data.disableAnimations); + expect(updatedData.reduceMotion, data.reduceMotion); expect(updatedData.boldText, data.boldText); expect(updatedData.highContrast, data.highContrast); expect(updatedData.onOffSwitchLabels, data.onOffSwitchLabels); @@ -791,6 +803,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -828,6 +841,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -867,6 +881,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -901,6 +916,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -940,6 +956,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -977,6 +994,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -1016,6 +1034,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -1050,6 +1069,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -1089,6 +1109,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -1126,6 +1147,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -1165,6 +1187,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -1199,6 +1222,7 @@ void main() { expect(unpadded.accessibleNavigation, true); expect(unpadded.invertColors, true); expect(unpadded.disableAnimations, true); + expect(unpadded.reduceMotion, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); expect(unpadded.onOffSwitchLabels, true); @@ -1531,6 +1555,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -1563,6 +1588,7 @@ void main() { expect(subScreenMediaQuery.accessibleNavigation, true); expect(subScreenMediaQuery.invertColors, true); expect(subScreenMediaQuery.disableAnimations, true); + expect(subScreenMediaQuery.reduceMotion, true); expect(subScreenMediaQuery.boldText, true); expect(subScreenMediaQuery.highContrast, true); expect(subScreenMediaQuery.onOffSwitchLabels, true); @@ -1610,6 +1636,7 @@ void main() { accessibleNavigation: true, invertColors: true, disableAnimations: true, + reduceMotion: true, boldText: true, highContrast: true, onOffSwitchLabels: true, @@ -1648,6 +1675,7 @@ void main() { expect(subScreenMediaQuery.accessibleNavigation, true); expect(subScreenMediaQuery.invertColors, true); expect(subScreenMediaQuery.disableAnimations, true); + expect(subScreenMediaQuery.reduceMotion, true); expect(subScreenMediaQuery.boldText, true); expect(subScreenMediaQuery.highContrast, true); expect(subScreenMediaQuery.onOffSwitchLabels, true); @@ -1929,6 +1957,11 @@ void main() { MediaQuery.maybeDisableAnimationsOf, MediaQueryData(disableAnimations: true), ), + const _MediaQueryAspectCase(MediaQuery.reduceMotionOf, MediaQueryData(reduceMotion: true)), + const _MediaQueryAspectCase( + MediaQuery.maybeReduceMotionOf, + MediaQueryData(reduceMotion: true), + ), const _MediaQueryAspectCase(MediaQuery.boldTextOf, MediaQueryData(boldText: true)), const _MediaQueryAspectCase(MediaQuery.maybeBoldTextOf, MediaQueryData(boldText: true)), const _MediaQueryAspectCase( From 5b75e35fafa71ee952283051cc37b60f8e7a4f4b Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 30 Jul 2026 21:49:02 -0400 Subject: [PATCH 134/147] Roll Skia from a1109355de78 to 07f08414dd66 (5 revisions) (#190328) https://skia.googlesource.com/skia.git/+log/a1109355de78..07f08414dd66 2026-07-31 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2026-07-30 kjlubick@google.com Avoid timeout in SkRegion::setPath 2026-07-30 thomsmit@google.com Add SkLogHandler to SKSLC Bazel 2026-07-30 alexisdavidc@google.com [SkCapture] Fix bug with zoom in viewer and fCaptureEnable 2026-07-30 nscobie@google.com [graphite] Fix swapped Vulkan instance version vs. physical device version during init If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 6381e8e5ac01c..4f4a7661938bf 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'a1109355de783681285de90d047a50109e4c2917', + 'skia_revision': '07f08414dd66db51d737619a9cab7353a5733db7', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 29e612e26c1df42e741262874af6529e52c0f441 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 31 Jul 2026 11:53:06 +0900 Subject: [PATCH 135/147] iOS: Configure test task runner thread at display QoS (#190283) The iOS unit tests for VSyncClient run it on a worker thread and block the test thread waiting on that worker. Xcode's Thread Performance Checker flags this as a priority inversion: the waiting thread runs at a higher quality-of-service class than the worker it is blocked on, producing the following error message: Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions FlutterFMLTaskRunnerTestHelper creates the worker with a bare fml::Thread, which by default is configured only with a thread name but leaves the thread at the default QoS. In production, the platform/UI thread that a VSyncClient posts to is created at ThreadPriority::kDisplay (user-interactive) by IOSPlatformThreadConfigSetter in FlutterEngine.mm, so the test worker was running below the priority of the real thread it's attempting to represent in the tests. This adds a config setter to raises it to QOS_CLASS_USER_INTERACTIVE (i.e. kDisplay). This prevents the priority inversion since the worker is now at least as high-priority as any thread that waits on it, which accurately reflects how it's used in the real embedder. Issue: https://github.com/flutter/flutter/issues/181684 Issue: https://github.com/flutter/flutter/issues/112232 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../Source/FlutterFMLTaskRunnerTestHelper.mm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.mm index ba0e8fa8c1dcc..854b566434d60 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.mm @@ -4,10 +4,19 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.h" +#include + #include "flutter/fml/message_loop.h" #include "flutter/fml/thread.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunner+FML.h" +// Set the thread to user-interactive QoS to match the real value used in +// the actual embedder. +static void ConfigureThread(const fml::Thread::ThreadConfig& config) { + fml::Thread::SetCurrentThreadName(config); + pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); +} + // A FlutterFMLTaskRunner that owns the fml::Thread it runs on. @interface FlutterFMLThreadTaskRunner : FlutterFMLTaskRunner @end @@ -17,7 +26,8 @@ @implementation FlutterFMLThreadTaskRunner { } - (instancetype)initWithLabel:(NSString*)label { - _thread = std::make_unique(label.UTF8String); + _thread = std::make_unique(ConfigureThread, + fml::Thread::ThreadConfig(label ? label.UTF8String : "")); self = [super initWithTaskRunner:_thread->GetTaskRunner()]; return self; } From 031c6b8463a1c75d7770f862a472438ac714d296 Mon Sep 17 00:00:00 2001 From: zhongliugo Date: Thu, 30 Jul 2026 21:56:09 -0700 Subject: [PATCH 136/147] [web] Autofill all AutofillGroup fields in semantics mode (#187108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Fixes #180652** **Problem** With a screen reader on, picking a saved login only filled the field you tapped. The other fields stayed empty. Browsers autofill a login by putting all its fields in one hidden `
`. But in screen-reader mode each field has to live in its own spot for accessibility, so it never went into that form. The browser saw a half-built login and filled only one field. **Fix** Still build the hidden form, but instead of moving the field into it, just link them with the HTML `form=""` attribute. The field stays where the screen reader needs it, and the browser now sees a complete login and fills every field. **Demo** Semantics ON: - Before: https://flutter-demo-30-before.web.app — only email fills - After: https://flutter-demo-30-after.web.app — both fields fill Semantics OFF (regression check, both behave the same): - Before: https://flutter-demo-31-before.web.app - After: https://flutter-demo-31-after.web.app **Known limitation** Chrome on iOS still fills only the username, not the password. That is a pre-existing WebKit limitation unrelated to this change (it also happens without a screen reader) and is tracked separately in #185327. --------- Co-authored-by: Harry Terkelsen <1961493+harryterkelsen@users.noreply.github.com> --- .../lib/src/engine/semantics/text_field.dart | 88 ++++- .../src/engine/text_editing/text_editing.dart | 149 +++++++- .../engine/semantics/text_field_test.dart | 339 ++++++++++++++++++ 3 files changed, 561 insertions(+), 15 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart index 7a8a78bb5d331..97ece97a2a7a5 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:ui/ui.dart' as ui; +import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../dom.dart'; import '../platform_dispatcher.dart'; @@ -47,6 +48,13 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { /// Current input configuration supplied by the "flutter/textinput" channel. InputConfiguration? inputConfig; + /// Whether an autofill form has been woken up for the active field. + /// + /// Tracked locally because the base strategy's `_appendedToForm` is private + /// to its library, and [SemanticsTextEditingStrategy] fully overrides + /// [disable] (it never calls `super.disable()`). + bool _formIsActive = false; + /// The semantics implementation does not operate on DOM nodes, but only /// remembers the config and callbacks. This is because the DOM nodes are /// supplied in the semantics update and enabled by [activate]. @@ -114,6 +122,24 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { } subscriptions.clear(); lastEditingState = null; + + // The focused field is linked to the autofill form by the `form` + // attribute. On blur, detach it and leave a synthetic placeholder holding + // its value, then keep the form dormant in the DOM so the autofill context + // can still be submitted (credential save via + // `TextInput.finishAutofillContext`) and the group stays complete when + // another field is focused. + if (_formIsActive && inputConfiguration.autofillGroup != null) { + final EngineAutofillForm group = inputConfiguration.autofillGroup!; + if (inputConfiguration.autofill != null) { + group.demoteFocusedToSynthetic(activeDomElement, inputConfiguration.autofill!); + } + if (group.formElement != null) { + group.goDormant(); + } + _formIsActive = false; + } + EnginePlatformDispatcher.instance.viewManager.safeBlur(activeDomElement); domElement = null; activeTextField = null; @@ -146,8 +172,25 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { OnActionCallback? onAction, }) { isEnabled = true; - inputConfiguration = inputConfig; - applyConfiguration(inputConfig); + final EngineAutofillForm? autofillGroup = inputConfig.autofillGroup; + inputConfiguration = autofillGroup == null + ? inputConfig + : inputConfig.copyWith( + autofillGroup: autofillGroup.copyWith(associateFocusedElementByAttribute: true), + ); + applyConfiguration(inputConfiguration); + + // Build the autofill form here, before [addEventHandlers] runs (it runs + // later in the same [enable] call). [addEventHandlers] subscribes to the + // `input` events of the synthetic group fields, so those fields must exist + // by then or non-focused fields would never propagate autofilled values. + // + // Note [placeElement]/[placeForm] are never reached via the normal + // placement path in semantics mode ([initializeElementPlacement] is a + // no-op), so the form must be set up explicitly here. + if (hasAutofillGroup) { + placeForm(); + } } @override @@ -165,7 +208,27 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { } @override - void placeForm() {} + void placeForm() { + // Safari autofills grouped credential fields by heuristic without needing a + // form. The attribute-linked form regresses that: a non-focused field's real + // input is left outside the form and stops being filled + // (flutter/flutter#180652). Skip the form on Safari and let its native + // heuristic fill the whole group. `_formIsActive` stays false, so [disable] + // skips the demote/dormant cleanup too. + // + // Other WebKit browsers (Chrome, Firefox on iOS) do not fill by heuristic + // and need the form path, so they are not skipped here. + if (ui_web.browser.isSafari) { + return; + } + + // The focused element is the real semantics-owned ``. It must not be + // moved into the form (that regressed a11y tab traversal, see + // flutter/flutter#180652). Link it to the form via the `form` attribute + // instead. See [EngineAutofillForm.wakeUp]. + inputConfiguration.autofillGroup!.wakeUp(activeDomElement, inputConfiguration.autofill!); + _formIsActive = true; + } @override void updateElementPlacement(EditableTextGeometry textGeometry) { @@ -364,6 +427,17 @@ class SemanticTextField extends SemanticRole { (editableElement as DomElementWithDisabledProperty).disabled = !semanticsObject.isEnabled; } + /// Whether an autofill group owns the autofill-related attributes of this + /// field. + /// + /// When the field participates in an autofill group, [AutofillInfo.applyToDomElement] + /// sets the element's `name` (and `id`/`autocomplete`) to the autofill hint. + /// A plain semantic input never has a `name`, so a non-empty `name` is a + /// reliable, order-independent signal that [_updateInputType] must not + /// overwrite `autocomplete`, otherwise grouped autofill silently breaks on + /// the next semantics update (flutter/flutter#180652). + bool get _isAutofillOwned => editableElement.getAttribute('name')?.isNotEmpty ?? false; + void _updateInputType() { if (semanticsObject.flags.isMultiline) { // text area can't be annotated with input type @@ -379,7 +453,9 @@ class SemanticTextField extends SemanticRole { // proper selection/cursor operations. input.removeAttribute('inputmode'); input.removeAttribute('autocapitalize'); - input.autocomplete = 'off'; + if (!_isAutofillOwned) { + input.autocomplete = 'off'; + } input.type = 'text'; switch (semanticsObject.inputType) { @@ -392,7 +468,9 @@ class SemanticTextField extends SemanticRole { case ui.SemanticsInputType.email: input.setAttribute('inputmode', 'email'); input.setAttribute('autocapitalize', 'none'); - input.autocomplete = 'email'; + if (!_isAutofillOwned) { + input.autocomplete = 'email'; + } default: } } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index f737daf67c728..e907a3e98469f 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -192,6 +192,7 @@ class EngineAutofillForm { required this.items, required this.formIdentifier, required this.focusedElementId, + this.associateFocusedElementByAttribute = false, }); DomHTMLFormElement? formElement; @@ -218,10 +219,40 @@ class EngineAutofillForm { final String focusedElementId; + /// Whether the real focused text field lives outside [formElement] and is + /// associated with it via the HTML `form` attribute. + /// + /// This is used by semantics mode, where moving the real text field into the + /// form would break accessibility traversal. + final bool associateFocusedElementByAttribute; + bool get _isSafariStrategy => textEditing.strategy is SafariDesktopTextEditingStrategy || textEditing.strategy is IOSTextEditingStrategy; + /// Whether the current browser engine is WebKit (Safari and all iOS + /// browsers). + /// + /// WebKit refuses to autofill an input that is zero-sized, so synthetic + /// autofill fields must keep a real size on WebKit, while other engines + /// tolerate zero-sized fields. An engine check is used rather than the active + /// text editing strategy because in semantics mode the active strategy is + /// [SemanticsTextEditingStrategy], not the Safari/iOS strategy, so the + /// strategy type alone would miss WebKit there. + bool get _isWebKit => ui_web.browser.browserEngine == ui_web.BrowserEngine.webkit; + + /// Stable DOM id for the form, used so a focused element that lives outside + /// the form, in semantics mode, can be associated with it via the HTML `form` + /// attribute. + /// + /// Derived from [formIdentifier] so it stays the same when the form goes + /// dormant and is woken again by a new instance, and so that distinct + /// identifiers map to distinct ids. The latter matters when multiple autofill + /// forms coexist in one document, for example multi-view, multiple autofill + /// groups, or a dormant form left in the DOM, where a focused element must + /// never be associated with the wrong form. + String get formDomId => 'flt-af-$formIdentifier'; + /// Creates an [EngineAutofillForm] from the JSON representation of a Flutter /// framework `TextInputConfiguration` object. /// @@ -289,6 +320,17 @@ class EngineAutofillForm { ); } + EngineAutofillForm copyWith({bool? associateFocusedElementByAttribute}) { + return EngineAutofillForm( + viewId: viewId, + items: items, + formIdentifier: formIdentifier, + focusedElementId: focusedElementId, + associateFocusedElementByAttribute: + associateFocusedElementByAttribute ?? this.associateFocusedElementByAttribute, + ); + } + static String _getFormIdentifier(Map items) { final ids = []; for (final FieldItem item in items.values) { @@ -301,7 +343,16 @@ class EngineAutofillForm { /// Wakes up the form with the given focused element. /// - /// The [focusedElement] is inserted into the form, replacing the old focused element. + /// The [focusedElement] is inserted into the form, replacing the old focused + /// element. + /// + /// When [associateFocusedElementByAttribute] is true (semantics mode), the focused + /// element is a real `` owned by the semantics tree and must stay in + /// its `` node. Moving it into the form regressed a11y tab + /// traversal (see flutter/flutter#180652). + /// Instead it is linked to the form via the HTML `form` attribute, and any + /// synthetic placeholder previously created for that field is removed so the + /// field is represented in the form exactly once. void wakeUp(DomHTMLElement focusedElement, AutofillInfo focusedAutofill) { // Since we're disabling pointer events on the form to fix Safari autofill, // we need to explicitly set pointer events on the active input element in @@ -311,7 +362,13 @@ class EngineAutofillForm { focusedElement.style.pointerEvents = 'all'; } - final EngineAutofillForm? existingForm = dormantForms[formIdentifier]; + EngineAutofillForm? existingForm = dormantForms[formIdentifier]; + if (existingForm != null && + existingForm.associateFocusedElementByAttribute != associateFocusedElementByAttribute) { + existingForm.formElement?.remove(); + dormantForms.remove(formIdentifier); + existingForm = null; + } final firstWakeUp = formElement == null; @@ -336,11 +393,24 @@ class EngineAutofillForm { } } - // There's potentially a new focused element that needs to be inserted into the existing form. - // - // Do not cause DOM disturbance unless necessary. Doing superfluous DOM operations may seem - // harmless, but it actually causes focus changes that could break things. - if (!formElement!.contains(focusedElement)) { + if (associateFocusedElementByAttribute) { + // Promote the focused field to its real (semantics-owned) element: drop + // any synthetic placeholder so the field is not submitted twice, then + // link the real element to the form by attribute. + final DomElement? synthetic = elements[focusedAutofill.uniqueIdentifier]; + if (synthetic != null && synthetic != focusedElement) { + synthetic.remove(); + } + elements.remove(focusedAutofill.uniqueIdentifier); + focusedElement.setAttribute('form', formDomId); + } else if (!formElement!.contains(focusedElement)) { + // There's potentially a new focused element that needs to be inserted + // into the existing form. + // + // Do not cause DOM disturbance unless necessary. Doing superfluous DOM + // operations may seem harmless, but it actually causes focus changes that + // could break things. + // // Find the matching element and replace it with the new focused element. final DomElement oldFocusedElement = elements[focusedAutofill.uniqueIdentifier]!; elements[focusedAutofill.uniqueIdentifier] = focusedElement; @@ -350,6 +420,40 @@ class EngineAutofillForm { _updateFieldValues(); } + /// Demotes a field that is losing focus in semantics mode back to a synthetic + /// in-form placeholder. + /// + /// In semantics mode the focused field is represented by its real element via + /// the `form` attribute (see [wakeUp]). When it blurs, the real element must + /// be detached from the form and replaced by a synthetic element carrying its + /// last value, so the field still participates in form submission (credential + /// save via `TextInput.finishAutofillContext`) and stays grouped when another + /// field in the group is focused. + void demoteFocusedToSynthetic(DomHTMLElement realElement, AutofillInfo autofill) { + realElement.removeAttribute('form'); + final String id = autofill.uniqueIdentifier; + final FieldItem? field = items[id]; + if (field == null || formElement == null) { + return; + } + if (elements[id] != null && elements[id] != realElement) { + // A synthetic placeholder already represents this field. + return; + } + final DomHTMLElement synthetic = field.inputType.createDomElement(); + field.autofillInfo.applyToDomElement(synthetic); + // Preserve the value the user (or autofill) just put in the real element so + // the field still submits correctly for credential save. + EditingState.fromDomElement(realElement).applyTextToDomElement(synthetic); + _styleAutofillElements( + synthetic, + shouldHideElement: !_isWebKit, + shouldDisablePointerEvents: _isWebKit, + ); + elements[id] = synthetic; + formElement!.append(synthetic); + } + /// Makes the form dormant. /// /// A dormant form stays in the DOM and does not interact with the framework until it's woken up. @@ -376,16 +480,25 @@ class EngineAutofillForm { formElement.noValidate = true; formElement.method = 'post'; formElement.action = '#'; + formElement.id = formDomId; formElement.addEventListener('submit', preventDefaultListener); // We need to explicitly disable pointer events on the form in Safari Desktop and iOS, // so that we don't have pointer event collisions if users hover over or click // into the invisible autofill elements within the form. - _styleAutofillElements(formElement, shouldDisablePointerEvents: _isSafariStrategy); + _styleAutofillElements(formElement, shouldDisablePointerEvents: _isWebKit); for (final FieldItem field in items.values) { final DomHTMLElement htmlElement; if (field.autofillInfo.uniqueIdentifier == focusedAutofill.uniqueIdentifier) { + if (associateFocusedElementByAttribute) { + // The focused element is a real semantics-owned element that stays + // in its `` node and is linked via the `form` + // attribute by [wakeUp]. Do not create or append a synthetic + // placeholder for it here, otherwise the field would be submitted + // twice. + continue; + } // Do not create the focused element here since it is created already. Use the provided one. htmlElement = focusedElement; } else { @@ -400,8 +513,8 @@ class EngineAutofillForm { // sized and placed on the DOM, we also have to disable pointer events. _styleAutofillElements( htmlElement, - shouldHideElement: !_isSafariStrategy, - shouldDisablePointerEvents: _isSafariStrategy, + shouldHideElement: !_isWebKit, + shouldDisablePointerEvents: _isWebKit, ); } @@ -1146,6 +1259,22 @@ class InputConfiguration { enableInteractiveSelection = flutterInputConfiguration.tryBool('enableInteractiveSelection') ?? true; + InputConfiguration copyWith({EngineAutofillForm? autofillGroup}) { + return InputConfiguration( + viewId: viewId, + inputType: inputType, + inputAction: inputAction, + obscureText: obscureText, + readOnly: readOnly, + autocorrect: autocorrect, + textCapitalization: textCapitalization, + autofill: autofill, + autofillGroup: autofillGroup ?? this.autofillGroup, + enableDeltaModel: enableDeltaModel, + enableInteractiveSelection: enableInteractiveSelection, + ); + } + /// The ID of the view that contains the text field. final int viewId; diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart index 20ea90ccad1c8..d0993e7587ebe 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -11,6 +11,7 @@ import 'package:ui/src/engine.dart' hide window; import 'package:ui/ui.dart' as ui; import 'package:ui/ui_web/src/ui_web.dart' as ui_web; +import '../../common/spy.dart'; import '../../common/test_initialization.dart'; import 'semantics_tester.dart'; @@ -613,6 +614,344 @@ void testMain() { expect(textField.editableElement.getAttribute('aria-description'), isNull); }); }); + + // Group autofill in semantics mode. See https://github.com/flutter/flutter/issues/180652 + group('$SemanticsTextEditingStrategy autofill group', () { + late HybridTextEditing testTextEditing; + late SemanticsTextEditingStrategy strategy; + + setUp(() { + testTextEditing = HybridTextEditing(); + SemanticsTextEditingStrategy.ensureInitialized(testTextEditing); + strategy = SemanticsTextEditingStrategy.instance; + testTextEditing.debugTextEditingStrategyOverride = strategy; + testTextEditing.configuration = singlelineConfig; + semantics() + ..debugOverrideTimestampFunction(() => _testTime) + ..semanticsEnabled = true; + }); + + tearDown(() { + if (strategy.isEnabled) { + strategy.disable(); + } + cleanForms(); + semantics().semanticsEnabled = false; + domDocument.activeElement?.blur(); + }); + + // Builds a focused-username + password autofill group and drives the + // semantics path so the focused field is activated. + ({EngineAutofillForm form, SemanticTextField textField}) activateGroup() { + final List> fields = _autofillFields( + ['username', 'password'], + ['field1', 'field2'], + ); + final focusedMap = fields.first['autofill']! as Map; + final EngineAutofillForm form = EngineAutofillForm.fromFrameworkMessage( + kImplicitViewId, + focusedMap, + fields, + )!; + final config = InputConfiguration( + viewId: kImplicitViewId, + autofill: AutofillInfo.fromFrameworkMessage(focusedMap), + autofillGroup: form, + ); + strategy.enable(config, onChange: (_, _) {}, onAction: (_) {}); + final SemanticsObject semanticsObject = createTextFieldSemantics(value: '', isFocused: true); + return ( + form: strategy.inputConfiguration.autofillGroup!, + textField: semanticsObject.semanticRole! as SemanticTextField, + ); + } + + test('builds the form and links the focused field by attribute', () { + final (form: EngineAutofillForm form, textField: SemanticTextField textField) = + activateGroup(); + final DomHTMLFormElement formElement = form.formElement!; + + // Form is inserted into the text-editing host with the stable id used + // for the form= association. + expect(form.associateFocusedElementByAttribute, isTrue); + expect(flutterView.dom.textEditingHost.contains(formElement), isTrue); + expect(formElement.id, form.formDomId); + expect(formElement.getElementsByClassName('submitBtn'), hasLength(1)); + + // The non-focused member is a synthetic placeholder inside the form. + final DomHTMLElement password = form.elements['field2']!; + expect(formElement.contains(password), isTrue); + expect((password as DomHTMLInputElement).name, 'current-password'); + + // The focused member is NOT synthesized into the form (would be + // submitted twice), it is linked by attribute instead. + expect(form.elements['field1'], isNull); + expect(textField.editableElement.getAttribute('form'), form.formDomId); + + // Regression guard for the 2021 tab-traversal regression + // (flutter/engine#25797): the editing element must stay in the + // semantics host and must NOT be moved into the form / text-editing + // host. + expect(flutterView.dom.semanticsHost.contains(textField.editableElement), isTrue); + expect(formElement.contains(textField.editableElement), isFalse); + + // Autofill hint is applied and not clobbered back to 'off' by + // _updateInputType during the same semantics update. + expect((textField.editableElement as DomHTMLInputElement).autocomplete, 'username'); + }); + + test('autocomplete survives a later semantics update', () { + final (form: EngineAutofillForm form, textField: SemanticTextField textField) = + activateGroup(); + expect((textField.editableElement as DomHTMLInputElement).autocomplete, 'username'); + + // A second semantics update re-runs _updateInputType; it must not stomp + // the autofill hint while the field is the active group member. + createTextFieldSemantics(value: '', isFocused: true); + expect((textField.editableElement as DomHTMLInputElement).autocomplete, 'username'); + expect(textField.editableElement.getAttribute('form'), form.formDomId); + }); + + test('autofill on a synthetic sibling propagates to the framework', () { + final spy = PlatformMessagesSpy()..setUp(); + try { + final (form: EngineAutofillForm form, textField: _) = activateGroup(); + final password = form.elements['field2']! as DomHTMLInputElement; + + // Simulate the browser autofilling the (non-focused) password field. + password.value = 'p4ssw0rd'; + password.dispatchEvent(createDomEvent('Event', 'input')); + + final Iterable tagged = spy.messages.where( + (m) => + m.channel == 'flutter/textinput' && + m.methodName == 'TextInputClient.updateEditingStateWithTag', + ); + expect(tagged, isNotEmpty); + final args = tagged.last.methodArguments as List; + expect(args[1], isA>()); + expect((args[1] as Map).containsKey('field2'), isTrue); + } finally { + spy.tearDown(); + } + }); + + test('demotes the focused field to a synthetic placeholder on blur', () { + final (form: EngineAutofillForm form, textField: SemanticTextField textField) = + activateGroup(); + expect(textField.editableElement.getAttribute('form'), form.formDomId); + + strategy.disable(); + + // The real element is detached from the form... + expect(textField.editableElement.getAttribute('form'), isNull); + // ...and replaced by a synthetic placeholder so the field still submits + // for credential save, and the form is kept dormant in the DOM. + final DomHTMLElement? placeholder = form.elements['field1']; + expect(placeholder, isNotNull); + expect(form.formElement!.contains(placeholder), isTrue); + expect(dormantForms[form.formIdentifier], form); + }); + + // Focus A, autofill A, focus B, autofill a different credential, focus A + // again, then assert each field is represented in the form exactly once + // with its latest value and the focused field is linked by attribute (not + // duplicated). This is the property that makes TextInput.finishAutofillContext + // submit correct values. Exercises wakeUp dormant-reuse, demote, and promote + // together. See https://github.com/flutter/flutter/issues/180652 + test('promote/demote keeps each field represented once across A->B->A', () { + // Each focus change arrives as a fresh InputConfiguration whose `autofill` + // is the focused field and whose group shares the same formIdentifier, so + // it reuses the dormant form. `values` mirrors how the framework re-sends + // the config with each field's current editing value after an autofilled + // value has propagated back. Non-focused synthetic values come from this + // editing state via _updateFieldValues, not from DOM scraping, so this is + // the faithful way to simulate autofill. + InputConfiguration configFor(int focusedIndex, List values) { + final List> f = _autofillFields( + ['username', 'password'], + ['field1', 'field2'], + values: values, + ); + final focusedMap = f[focusedIndex]['autofill']! as Map; + return InputConfiguration( + viewId: kImplicitViewId, + autofill: AutofillInfo.fromFrameworkMessage(focusedMap), + autofillGroup: EngineAutofillForm.fromFrameworkMessage(kImplicitViewId, focusedMap, f), + ); + } + + final tester = SemanticsTester(owner()); + void focusNode(int nodeId) { + tester.updateNode( + id: 0, + children: [ + tester.updateNode( + id: 1, + flags: ui.SemanticsFlags( + isEnabled: ui.Tristate.isTrue, + isTextField: true, + isFocused: nodeId == 1 ? ui.Tristate.isTrue : ui.Tristate.isFalse, + ), + value: '', + rect: const ui.Rect.fromLTRB(0, 0, 50, 10), + ), + tester.updateNode( + id: 2, + flags: ui.SemanticsFlags( + isEnabled: ui.Tristate.isTrue, + isTextField: true, + isFocused: nodeId == 2 ? ui.Tristate.isTrue : ui.Tristate.isFalse, + ), + value: '', + rect: const ui.Rect.fromLTRB(0, 20, 50, 10), + ), + ], + ); + tester.apply(); + } + + // Node 1 == field1 (username/A), node 2 == field2 (password/B). + // Focus A. Nothing autofilled yet. + strategy.enable(configFor(0, ['', '']), onChange: (_, _) {}, onAction: (_) {}); + focusNode(1); + final EngineAutofillForm formA = strategy.inputConfiguration.autofillGroup!; + // Browser autofills the focused field A. Its live value is what save + // submits for the focused, form-associated element. + (tester.getTextField(1).editableElement as DomHTMLInputElement).value = 'userA'; + + // Focus B. The framework now knows A's value and re-sends the config. + strategy.enable(configFor(1, ['userA', '']), onChange: (_, _) {}, onAction: (_) {}); + focusNode(2); + (tester.getTextField(2).editableElement as DomHTMLInputElement).value = 'passB'; + + // Focus A again. The framework now knows both values. + strategy.enable( + configFor(0, ['userA', 'passB']), + onChange: (_, _) {}, + onAction: (_) {}, + ); + focusNode(1); + + final EngineAutofillForm group = strategy.inputConfiguration.autofillGroup!; + final DomHTMLFormElement formElement = group.formElement!; + + // Same DOM form reused across every wake/dormant cycle. + expect(formElement, formA.formElement); + + // A is focused: real element linked by attribute, never moved into or + // duplicated in the form. + expect(tester.getTextField(1).editableElement.getAttribute('form'), group.formDomId); + expect(formElement.contains(tester.getTextField(1).editableElement), isFalse); + expect((tester.getTextField(1).editableElement as DomHTMLInputElement).value, 'userA'); + + // B is not focused: exactly one synthetic carrying its latest value, no + // stale/duplicate synthetic for either field, B's real element released. + final List synthetics = formElement + .querySelectorAll('input') + .cast() + .where((DomHTMLInputElement e) => e.type != 'submit') + .toList(); + expect(synthetics, hasLength(1)); + expect(synthetics.single.name, 'current-password'); + expect(synthetics.single.value, 'passB'); + expect(group.elements.length, 1); + expect(group.elements['field2'], synthetics.single); + expect(group.elements.containsKey('field1'), isFalse); + expect(tester.getTextField(2).editableElement.getAttribute('form'), isNull); + }); + // The attribute-linked form is only built on non-Safari browsers. Safari + // fills grouped fields by its own heuristic, so the form is intentionally + // skipped there (see [SemanticsTextEditingStrategy.initializeTextEditing]). + // The Safari path is covered by the group below. + }, skip: ui_web.browser.isSafari); + + // On Safari the autofill form is not built; the native heuristic fills the + // group instead. See https://github.com/flutter/flutter/issues/180652 + group('$SemanticsTextEditingStrategy autofill group on Safari', () { + late HybridTextEditing testTextEditing; + late SemanticsTextEditingStrategy strategy; + + setUp(() { + testTextEditing = HybridTextEditing(); + SemanticsTextEditingStrategy.ensureInitialized(testTextEditing); + strategy = SemanticsTextEditingStrategy.instance; + testTextEditing.debugTextEditingStrategyOverride = strategy; + testTextEditing.configuration = singlelineConfig; + semantics() + ..debugOverrideTimestampFunction(() => _testTime) + ..semanticsEnabled = true; + }); + + tearDown(() { + if (strategy.isEnabled) { + strategy.disable(); + } + cleanForms(); + semantics().semanticsEnabled = false; + domDocument.activeElement?.blur(); + }); + + test('does not build the form and does not link the focused field', () { + final List> fields = _autofillFields( + ['username', 'password'], + ['field1', 'field2'], + ); + final focusedMap = fields.first['autofill']! as Map; + final EngineAutofillForm form = EngineAutofillForm.fromFrameworkMessage( + kImplicitViewId, + focusedMap, + fields, + )!; + final config = InputConfiguration( + viewId: kImplicitViewId, + autofill: AutofillInfo.fromFrameworkMessage(focusedMap), + autofillGroup: form, + ); + strategy.enable(config, onChange: (_, _) {}, onAction: (_) {}); + final SemanticsObject semanticsObject = createTextFieldSemantics(value: '', isFocused: true); + final textField = semanticsObject.semanticRole! as SemanticTextField; + + // No form is created and the focused field is not linked by attribute. + expect(form.formElement, isNull); + expect(textField.editableElement.getAttribute('form'), isNull); + }, skip: !ui_web.browser.isSafari); + }); +} + +/// Builds the `fields` list of a `TextInputConfiguration` autofill group, the +/// same shape the framework sends over the `flutter/textinput` channel. +List> _autofillFields( + List hints, + List uniqueIds, { + List? values, +}) { + assert(hints.length == uniqueIds.length); + assert(values == null || values.length == hints.length); + return >[ + for (var i = 0; i < hints.length; i++) + { + 'inputType': { + 'name': 'TextInputType.text', + 'signed': null, + 'decimal': null, + }, + 'textCapitalization': 'TextCapitalization.none', + 'autofill': { + 'uniqueIdentifier': uniqueIds[i], + 'hints': [hints[i]], + 'editingValue': { + 'text': values?[i] ?? '', + 'selectionBase': 0, + 'selectionExtent': 0, + 'selectionAffinity': 'TextAffinity.downstream', + 'selectionIsDirectional': false, + 'composingBase': -1, + 'composingExtent': -1, + }, + }, + }, + ]; } SemanticsObject createTextFieldSemantics({ From d1b17eda02a551efcb3bc0b271dcf5963137f885 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 31 Jul 2026 02:44:19 -0400 Subject: [PATCH 137/147] Roll Skia from 07f08414dd66 to dcbd015c9646 (1 revision) (#190333) https://skia.googlesource.com/skia.git/+log/07f08414dd66..dcbd015c9646 2026-07-31 skia-autoroll@skia-public.iam.gserviceaccount.com Roll bazel_build from d3af3547496f60d9c... to 782dfac458428f99c... (1 revision) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4f4a7661938bf..72168a1f491df 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '07f08414dd66db51d737619a9cab7353a5733db7', + 'skia_revision': 'dcbd015c96464eeca6f67992cffd7f788ab6a769', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 56b8e1a851a594b1a154f8ea93270807dab22b9a Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 31 Jul 2026 04:39:01 -0400 Subject: [PATCH 138/147] Roll Dart SDK from 96f49fc43f59 to c3acfc2479f6 (1 revision) (#190336) https://dart.googlesource.com/sdk.git/+log/96f49fc43f59..c3acfc2479f6 2026-07-31 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.14.0-81.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC aaclarke@google.com,dart-vm-team@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/DEPS b/DEPS index 72168a1f491df..21b384aa4b8fa 100644 --- a/DEPS +++ b/DEPS @@ -56,24 +56,24 @@ vars = { # updated revision list of existing dependencies. You will need to # gclient sync before and after update deps to ensure all deps are updated. # updated revision list of existing dependencies. - 'dart_revision': '96f49fc43f592fbaa41dc451f7896653385ac1c9', + 'dart_revision': 'c3acfc2479f6eae42f7cbafe5e5518a2dd757b81', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_binaryen_rev': '9926156a583cec3d22d521232b31c70fa9a87dc1', 'dart_boringssl_rev': '22a0079b189c391b95689813a41982ce11876f0a', - 'dart_core_rev': '25926e846003a123e5249df42c5a1ce30c246e3a', + 'dart_core_rev': 'fe516ee1b38cc60e7a8c6e082c337037a043d782', 'dart_devtools_rev': '21f1838f3a9b138ac377efb953ca5a53c8832e75', - 'dart_ecosystem_rev': '848b3bf3b757d2e9ae4d60030eeed5756c87783f', + 'dart_ecosystem_rev': 'edfdb3b4063b9034b708144633a700204f865f43', 'dart_http_rev': '5d94ef52582867e077bf41c3fa20fb8b1d1d834e', - 'dart_i18n_rev': 'd0683bdea253d19a4350f5bc2be9017aba61837f', + 'dart_i18n_rev': 'e1b5a798f8922bb27bbc6d858748ece6f9a19f02', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_rev': '91efb90f437bb6a30e6726c3369a2fcb9bba06e7', 'dart_pub_rev': 'ec276d10a7fa0f6c6ec005340fb9ad29f3b012d0', 'dart_sync_http_rev': '6666fff944221891182e1f80bf56569338164d72', - 'dart_tools_rev': '3f850c4fd27c3a19abd91889a8261e0bf2fd1663', + 'dart_tools_rev': 'b827a6e38b934232c7f7b8728a5aef6165e7df2d', 'dart_vector_math_rev': 'cf3b5db7340d317dd3489e5a35434b408020a852', - 'dart_web_rev': 'eb8c3fc61a1e35f48f865836c7c7342897d91bcc', + 'dart_web_rev': '12a9ca2ebc08f5a6f2d69aebc7daa1f5a2e6a431', 'dart_webdriver_rev': '3a711ebb36871eac997c5d5d2429f7414873dc63', 'dart_webkit_inspection_protocol_rev': '762115a971d1968bc940454ad1e88d506d8c5640', @@ -342,7 +342,7 @@ deps = { Var('dart_git') + '/dart_style.git@dfdf6420c7ea923d28edef3f11e89b4ff23d03bf', 'engine/src/flutter/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@1d56f263955f329b6701d8f84f069eb0aef353a4', + Var('dart_git') + '/dartdoc.git@ac96918074974dcd4ea20f764f17090f58c1e428', 'engine/src/flutter/third_party/dart/third_party/pkg/ecosystem': Var('dart_git') + '/ecosystem.git' + '@' + Var('dart_ecosystem_rev'), @@ -366,7 +366,7 @@ deps = { Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), 'engine/src/flutter/third_party/dart/third_party/pkg/shelf': - Var('dart_git') + '/shelf.git@71248e727317930f244c4b4535e9733bcfc66677', + Var('dart_git') + '/shelf.git@6918a7690946044b4098e9f6735439044c676e13', 'engine/src/flutter/third_party/dart/third_party/pkg/sync_http': Var('dart_git') + '/sync_http.git' + '@' + Var('dart_sync_http_rev'), @@ -393,7 +393,7 @@ deps = { Var('dart_git') + '/external/github.com/google/webkit_inspection_protocol.dart.git' + '@' + Var('dart_webkit_inspection_protocol_rev'), 'engine/src/flutter/third_party/dart/tools/sdks/dart-sdk': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/dart-sdk/${{platform}}', 'version': 'version:3.13.0-272.0.dev'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/dart-sdk/${{platform}}', 'version': 'version:3.14.0-75.0.dev'}]}, # WARNING: end of dart dependencies list that is cleaned up automatically - see create_updated_flutter_deps.py. From 866407a38f3e3786cc758d84bba8ee4c80dd147b Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 31 Jul 2026 17:53:36 +0900 Subject: [PATCH 139/147] macOS: Set static text accessibility role for read-only text fields (#190330) https://github.com/flutter/flutter/pull/184501 updated accessibility role mapping such that read-only text fields are mapped to `ax::mojom::Role::kTextField`. On macOS, read-only text fields are intentionally treated as static text. Because of the role change, the macOS embedder incorrectly instantiated `FlutterTextPlatformNode` for read-only text fields, leading to detached accessibility elements and test crashes. This updates the macOS platform node delegate to only instantiate a FlutterTextPlatformNode when the target text field is actually editable and not read-only or disabled. We now explicitly return read-only text fields as standard static text elements. This matches current behaviour but makes it explicit in the platform node delegate for macOS. We're also now calling loadView on the viewController in tests. In a running app, the view controller will always be loaded, so this better approximates reality in the tests... and avoids bugs such as those caught in https://github.com/flutter/flutter/pull/184501. This should allow us to reland https://github.com/flutter/flutter/pull/184501. Issue: https://github.com/flutter/flutter/issues/184559 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../Source/FlutterPlatformNodeDelegateMac.h | 4 ++++ .../Source/FlutterPlatformNodeDelegateMac.mm | 20 ++++++++++++++++--- .../FlutterPlatformNodeDelegateMacTest.mm | 12 +++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h index 62014f670ddf1..8ae3edfd99da0 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.h @@ -30,6 +30,9 @@ class FlutterPlatformNodeDelegateMac : public FlutterPlatformNodeDelegate { void NodeDataChanged(const ui::AXNodeData& old_node_data, const ui::AXNodeData& new_node_data) override; + // |ui::AXPlatformNodeDelegateBase| + const ui::AXNodeData& GetData() const override; + //--------------------------------------------------------------------------- /// @brief Gets the live region text of this node in UTF-8 format. This /// is useful to determine the changes in between semantics @@ -55,6 +58,7 @@ class FlutterPlatformNodeDelegateMac : public FlutterPlatformNodeDelegate { ui::AXPlatformNode* ax_platform_node_; std::weak_ptr bridge_; __weak FlutterViewController* view_controller_; + mutable ui::AXNodeData cached_data_; gfx::RectF ConvertBoundsFromLocalToScreen( const gfx::RectF& local_bounds) const; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.mm index 5a6c0235d0931..744a0c452a809 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMac.mm @@ -27,7 +27,7 @@ void FlutterPlatformNodeDelegateMac::Init(std::weak_ptr bridge, ui::AXNode* node) { FlutterPlatformNodeDelegate::Init(bridge, node); - if (GetData().IsTextField()) { + if (GetData().IsTextField() && !GetData().IsReadOnlyOrDisabled()) { ax_platform_node_ = new FlutterTextPlatformNode(this, view_controller_); } else { ax_platform_node_ = ui::AXPlatformNode::Create(this); @@ -37,15 +37,29 @@ void FlutterPlatformNodeDelegateMac::NodeDataChanged(const ui::AXNodeData& old_node_data, const ui::AXNodeData& new_node_data) { - if (old_node_data.IsTextField() && !new_node_data.IsTextField()) { + bool old_is_editable_textfield = + old_node_data.IsTextField() && !old_node_data.IsReadOnlyOrDisabled(); + bool new_is_editable_textfield = + new_node_data.IsTextField() && !new_node_data.IsReadOnlyOrDisabled(); + if (old_is_editable_textfield && !new_is_editable_textfield) { ax_platform_node_->Destroy(); ax_platform_node_ = ui::AXPlatformNode::Create(this); - } else if (!old_node_data.IsTextField() && new_node_data.IsTextField()) { + } else if (!old_is_editable_textfield && new_is_editable_textfield) { ax_platform_node_->Destroy(); ax_platform_node_ = new FlutterTextPlatformNode(this, view_controller_); } } +const ui::AXNodeData& FlutterPlatformNodeDelegateMac::GetData() const { + const ui::AXNodeData& data = FlutterPlatformNodeDelegate::GetData(); + if (data.IsTextField() && data.IsReadOnlyOrDisabled()) { + cached_data_ = data; + cached_data_.role = ax::mojom::Role::kStaticText; + return cached_data_; + } + return data; +} + FlutterPlatformNodeDelegateMac::~FlutterPlatformNodeDelegateMac() { // Destroy() also calls delete on itself. ax_platform_node_->Destroy(); diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm index 9509c03ccb40a..24c84818044b4 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm @@ -32,6 +32,7 @@ TEST(FlutterPlatformNodeDelegateMac, Basics) { FlutterViewController* viewController = CreateTestViewController(); FlutterEngine* engine = viewController.engine; + [viewController loadView]; engine.semanticsEnabled = YES; auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. @@ -60,9 +61,11 @@ // Verify the accessibility attribute matches. NSAccessibilityElement* native_accessibility = root_platform_node_delegate->GetNativeViewAccessible(); + ASSERT_NE(native_accessibility, nil); std::string value = [native_accessibility.accessibilityValue UTF8String]; EXPECT_TRUE(value == "accessibility"); - EXPECT_EQ(native_accessibility.accessibilityRole, NSAccessibilityStaticTextRole); + EXPECT_TRUE( + [native_accessibility.accessibilityRole isEqualToString:NSAccessibilityStaticTextRole]); EXPECT_EQ([native_accessibility.accessibilityChildren count], 0u); [engine shutDownEngine]; } @@ -70,6 +73,7 @@ TEST(FlutterPlatformNodeDelegateMac, SelectableTextHasCorrectSemantics) { FlutterViewController* viewController = CreateTestViewController(); FlutterEngine* engine = viewController.engine; + [viewController loadView]; engine.semanticsEnabled = YES; auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. @@ -99,9 +103,11 @@ // Verify the accessibility attribute matches. NSAccessibilityElement* native_accessibility = root_platform_node_delegate->GetNativeViewAccessible(); + ASSERT_NE(native_accessibility, nil); std::string value = [native_accessibility.accessibilityValue UTF8String]; EXPECT_EQ(value, "selectable text"); - EXPECT_EQ(native_accessibility.accessibilityRole, NSAccessibilityStaticTextRole); + EXPECT_TRUE( + [native_accessibility.accessibilityRole isEqualToString:NSAccessibilityStaticTextRole]); EXPECT_EQ([native_accessibility.accessibilityChildren count], 0u); NSRange selection = native_accessibility.accessibilitySelectedTextRange; EXPECT_EQ(selection.location, 1u); @@ -113,6 +119,7 @@ TEST(FlutterPlatformNodeDelegateMac, SelectableTextWithoutSelectionReturnZeroRange) { FlutterViewController* viewController = CreateTestViewController(); FlutterEngine* engine = viewController.engine; + [viewController loadView]; engine.semanticsEnabled = YES; auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. @@ -142,6 +149,7 @@ // Verify the accessibility attribute matches. NSAccessibilityElement* native_accessibility = root_platform_node_delegate->GetNativeViewAccessible(); + ASSERT_NE(native_accessibility, nil); NSRange selection = native_accessibility.accessibilitySelectedTextRange; EXPECT_TRUE(selection.location == NSNotFound); EXPECT_EQ(selection.length, 0u); From 1eb58465376d969d68a8c7e3e5a1c6fbbf594640 Mon Sep 17 00:00:00 2001 From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Date: Fri, 31 Jul 2026 04:37:25 -0700 Subject: [PATCH 140/147] Migrate platform_channel_swift example to material_ui (#190308) Migrates the `platform_channel_swift` example to import material_ui. Related to https://github.com/flutter/flutter/issues/190093. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- dev/bots/check_examples_cross_imports.dart | 1 - examples/platform_channel_swift/lib/main.dart | 2 +- examples/platform_channel_swift/pubspec.yaml | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/bots/check_examples_cross_imports.dart b/dev/bots/check_examples_cross_imports.dart index 74968f54f8a0e..c0bbe0a117a00 100644 --- a/dev/bots/check_examples_cross_imports.dart +++ b/dev/bots/check_examples_cross_imports.dart @@ -585,7 +585,6 @@ class ExamplesCrossImportChecker { 'examples/multiple_windows/lib/app/window_settings_dialog.dart', 'examples/multiple_windows/lib/main.dart', 'examples/multiple_windows/test/multiple_windows_test.dart', - 'examples/platform_channel_swift/lib/main.dart', 'examples/platform_view/lib/main.dart', 'examples/splash/lib/main.dart', 'examples/splash/test/splash_test.dart', diff --git a/examples/platform_channel_swift/lib/main.dart b/examples/platform_channel_swift/lib/main.dart index febf360c2cbad..c8d12af8e7815 100644 --- a/examples/platform_channel_swift/lib/main.dart +++ b/examples/platform_channel_swift/lib/main.dart @@ -4,8 +4,8 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:material_ui/material_ui.dart'; class PlatformChannel extends StatefulWidget { const PlatformChannel({super.key}); diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 3888319e33f96..e7ec8dec2ae46 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -8,6 +8,7 @@ resolution: workspace dependencies: flutter: sdk: flutter + material_ui: ^0.0.2 dev_dependencies: @@ -21,4 +22,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: f9g2jl +# PUBSPEC CHECKSUM: kujv36 From 6a509a03ac25cb53d0499fed0ed774cc9cf7f2cc Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 31 Jul 2026 12:51:50 +0000 Subject: [PATCH 141/147] Remove the --enable-serial-gc engine flag (#190310) This flag was used to experiment with Dart GC performance. But it is not actively being used and is no longer functioning as intended in Dart. The flag was causing issues in recent Dart SDK rolls (see https://github.com/flutter/flutter/pull/190266) --- engine/src/flutter/common/settings.h | 1 - engine/src/flutter/runtime/dart_vm.cc | 17 -------------- engine/src/flutter/shell/common/switch_defs.h | 6 ----- engine/src/flutter/shell/common/switches.cc | 3 --- engine/src/flutter/testing/dart/BUILD.gn | 1 - .../flutter/testing/dart/serial_gc_test.dart | 22 ------------------- 6 files changed, 50 deletions(-) delete mode 100644 engine/src/flutter/testing/dart/serial_gc_test.dart diff --git a/engine/src/flutter/common/settings.h b/engine/src/flutter/common/settings.h index 66af329c01358..d0c2628d241f3 100644 --- a/engine/src/flutter/common/settings.h +++ b/engine/src/flutter/common/settings.h @@ -160,7 +160,6 @@ struct Settings { bool enable_dart_profiling = false; bool profile_startup = false; bool disable_dart_asserts = false; - bool enable_serial_gc = false; bool profile_microtasks = false; // Whether embedder only allows secure connections. diff --git a/engine/src/flutter/runtime/dart_vm.cc b/engine/src/flutter/runtime/dart_vm.cc index 9545914254b89..42df4beadfb2a 100644 --- a/engine/src/flutter/runtime/dart_vm.cc +++ b/engine/src/flutter/runtime/dart_vm.cc @@ -43,16 +43,6 @@ static const char* kDartAllConfigsArgs[] = { static const char* kDartPrecompilationArgs[] = {"--precompilation"}; -static const char* kSerialGCArgs[] = { - // clang-format off - "--concurrent_mark=false", - "--concurrent_sweep=false", - "--compactor_tasks=1", - "--scavenger_tasks=0", - "--marker_tasks=0", - // clang-format on -}; - [[maybe_unused]] static const char* kDartWriteProtectCodeArgs[] = { "--no_write_protect_code", @@ -367,13 +357,6 @@ DartVM::DartVM(const std::shared_ptr& vm_data, PushBackAll(&args, kDartAssertArgs, std::size(kDartAssertArgs)); } - // On low power devices with lesser number of cores, using concurrent - // marking or sweeping causes contention for the UI thread leading to - // Jank, this option can be used to turn off all concurrent GC activities. - if (settings_.enable_serial_gc) { - PushBackAll(&args, kSerialGCArgs, std::size(kSerialGCArgs)); - } - if (settings_.start_paused) { PushBackAll(&args, kDartStartPausedArgs, std::size(kDartStartPausedArgs)); } diff --git a/engine/src/flutter/shell/common/switch_defs.h b/engine/src/flutter/shell/common/switch_defs.h index 696b43a1dae59..d95789073cb23 100644 --- a/engine/src/flutter/shell/common/switch_defs.h +++ b/engine/src/flutter/shell/common/switch_defs.h @@ -219,12 +219,6 @@ DEF_SWITCH(DisableDartAsserts, "disabled. This flag may be specified if the user wishes to run " "with assertions disabled in the debug product mode (i.e. with JIT " "or DBC).") -DEF_SWITCH(EnableSerialGC, - "enable-serial-gc", - "On low power devices with low core counts, running concurrent " - "GC tasks on threads can cause them to contend with the UI thread " - "which could potentially lead to jank. This option turns off all " - "concurrent GC activities") DEF_SWITCH(DisallowInsecureConnections, "disallow-insecure-connections", "By default, dart:io allows all socket connections. If this switch " diff --git a/engine/src/flutter/shell/common/switches.cc b/engine/src/flutter/shell/common/switches.cc index bffc7b3f9182b..9948072de2fcd 100644 --- a/engine/src/flutter/shell/common/switches.cc +++ b/engine/src/flutter/shell/common/switches.cc @@ -311,9 +311,6 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line, settings.trace_startup = command_line.HasOption(FlagForSwitch(Switch::TraceStartup)); - settings.enable_serial_gc = - command_line.HasOption(FlagForSwitch(Switch::EnableSerialGC)); - #if !FLUTTER_RELEASE settings.trace_skia = true; diff --git a/engine/src/flutter/testing/dart/BUILD.gn b/engine/src/flutter/testing/dart/BUILD.gn index 885d0ea2e55fa..71b41edf596a3 100644 --- a/engine/src/flutter/testing/dart/BUILD.gn +++ b/engine/src/flutter/testing/dart/BUILD.gn @@ -48,7 +48,6 @@ tests = [ "plugin_utilities_test.dart", "process_test.dart", "semantics_test.dart", - "serial_gc_test.dart", "spawn_helper.dart", "spawn_test.dart", "stringification_test.dart", diff --git a/engine/src/flutter/testing/dart/serial_gc_test.dart b/engine/src/flutter/testing/dart/serial_gc_test.dart deleted file mode 100644 index 4d7e580995812..0000000000000 --- a/engine/src/flutter/testing/dart/serial_gc_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// FlutterTesterOptions=--enable-serial-gc - -import 'package:test/test.dart'; - -int use(List a) { - return a[0]; -} - -void main() { - test('Serial GC option test ', () async { - const threw = false; - for (var i = 0; i < 100; i++) { - final a = [100]; - use(a); - } - expect(threw, false); - }); -} From 8608df9f95b14c54bfdb520030ba0af462cf52ff Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 31 Jul 2026 12:53:02 +0000 Subject: [PATCH 142/147] [Impeller] Do not acquire an unneeded image in KHRSwapchainVK if the swapchain must be recreated due to a size change (#190116) KHRSwapchainVK::AcquireNextDrawable was calling vkAcquireNextImageKHR regardless of whether the KHRSwapchainImplVK's size matched the current size of the KHRSwapchainVK. If there was a size change, then the resulting image would never be presented, which can cause resource leaks even after the old swapchain is destroyed. This PR avoids the call to KHRSwapchainImplVK::AcquireNextDrawable and vkAcquireNextImageKHR if there is a size mismatch. Fixes https://github.com/flutter/flutter/issues/190114 --- .../vulkan/swapchain/khr/khr_swapchain_vk.cc | 10 +++-- .../vulkan/test/swapchain_unittests.cc | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc index 339a01b1018fb..286a9f4355cbc 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc @@ -57,9 +57,13 @@ std::unique_ptr KHRSwapchainVK::AcquireNextDrawable( TRACE_EVENT0("impeller", __FUNCTION__); - auto result = impl_->AcquireNextDrawable(); - if (!result.out_of_date && size_ == impl_->GetSize()) { - return std::move(result.surface); + // Do not call AcquireNextDrawable if the impl_ has a mismatched size. + // Acquiring an image without presenting it can cause leaks. + if (size_ == impl_->GetSize()) { + auto result = impl_->AcquireNextDrawable(); + if (!result.out_of_date) { + return std::move(result.surface); + } } // When the swapchain says its out-of-date, we attempt to read the underlying diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc index 847ba0938a2f8..a346156c0c037 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/test/swapchain_unittests.cc @@ -163,5 +163,42 @@ TEST(SwapchainTest, NoFenceWaitAfterAcquireNextImageFailure) { EXPECT_FALSE(wait_for_fences_called); } +TEST(SwapchainTest, SwapchainSizeChangeDoesNotAcquireDrawable) { + int acquire_call_count = 0; + auto const context = + MockVulkanContextBuilder() + .SetAcquireNextImageCallback([&](VkDevice, VkSwapchainKHR, uint64_t, + VkSemaphore, VkFence, + uint32_t* pImageIndex) -> VkResult { + *pImageIndex = 0; + acquire_call_count++; + return VK_SUCCESS; + }) + .Build(); + + auto surface = CreateSurface(*context); + ISize original_size(1, 1); + SetSwapchainImageSize(original_size); + auto swapchain = + KHRSwapchainVK::Create(context, std::move(surface), original_size, + /*enable_msaa=*/false); + auto image = swapchain->AcquireNextDrawable(); + ASSERT_NE(image, nullptr); + EXPECT_EQ(image->GetSize(), original_size); + + ISize new_size(100, 100); + SetSwapchainImageSize(new_size); + swapchain->UpdateSurfaceSize(new_size); + + acquire_call_count = 0; + image = swapchain->AcquireNextDrawable(); + ASSERT_NE(image, nullptr); + EXPECT_EQ(image->GetSize(), new_size); + + // Verify that the call to AcquireNextDrawable after the resize did not make + // any extra calls to the underlying Vulkan API. + EXPECT_EQ(acquire_call_count, 1); +} + } // namespace testing } // namespace impeller From edc2eb076e33ffffbfdda3052ce8f062bec0f405 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 31 Jul 2026 09:47:24 -0400 Subject: [PATCH 143/147] Roll Skia from dcbd015c9646 to 7ef86a5b0eb9 (4 revisions) (#190347) https://skia.googlesource.com/skia.git/+log/dcbd015c9646..7ef86a5b0eb9 2026-07-31 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 4ffddf5fb2e7 to 9b3fec1aec6c (14 revisions) 2026-07-31 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 22ba45a57477 to 0e5d32f40590 (9 revisions) 2026-07-31 skia-autoroll@skia-public.iam.gserviceaccount.com Roll debugger-app-base from 8f7ec5882f46 to 2d7debe40c61 2026-07-31 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 7f1c4bcf56a3 to e5a5eb98eb1c (9 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 21b384aa4b8fa..1055e26f7a2e6 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': 'dcbd015c96464eeca6f67992cffd7f788ab6a769', + 'skia_revision': '7ef86a5b0eb91db85580bd71ebed3c4ba489d5ba', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 347b5a08cd8e41ccb74166eda22066f1ad23fa50 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 31 Jul 2026 11:29:13 -0400 Subject: [PATCH 144/147] Roll Packages from 7d17fc869daf to 5351d8c0f8df (5 revisions) (#190351) https://github.com/flutter/packages/compare/7d17fc869daf...5351d8c0f8df 2026-07-30 katelovett@google.com [material_ui, cupertino_ui] Add migration bridge utilities (flutter/packages#12314) 2026-07-30 269567208+reidbaker-agent@users.noreply.github.com Enforce scoped validation tools in AGENTS.md with file-based trap evals (flutter/packages#12297) 2026-07-30 engine-flutter-autoroll@skia.org Manual roll Flutter from c83f80b2367b to 2a230d1e8037 (6 revisions) (flutter/packages#12325) 2026-07-30 engine-flutter-autoroll@skia.org Manual roll Flutter from 0f0246377b1c to c83f80b2367b (7 revisions) (flutter/packages#12318) 2026-07-30 kirill.webmail@yandex.com [camera_android_camerax] Prefer higher resolution for max preset (flutter/packages#11956) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 8be15b87661b0..a6ebf2a65b6f0 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -7d17fc869daf572994367a750c626817a506d667 +5351d8c0f8df210f09d79aea5960a5d48ceb44af From e52f01c920adf32704069a1826ee98f75057edac Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Fri, 31 Jul 2026 16:14:08 +0000 Subject: [PATCH 145/147] [Impeller] Execute PipelineCompileQueue jobs in the order that they were added (#190262) ContentContext tries to create the most commonly used shaders first. But previously the PipelineCompileQueue was using an unordered map to store the shader compilation jobs, which made it iterate through the jobs in arbitrary order. This means that the queue may not build the most needed shaders earliest in the background. If these shaders are not built before the raster thread needs them, then the raster thread will need to spend time building them on demand. This PR changes PipelineCompileQueue to use an Abseil linked hash map that retains the order in which elements are inserted. PipelineCompileQueue will then build the shaders in the order that they were added by ContentContext. --- engine/src/flutter/impeller/renderer/BUILD.gn | 1 + .../renderer/pipeline_compile_queue.h | 11 ++++----- .../pipeline_compile_queue_unittests.cc | 24 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/BUILD.gn b/engine/src/flutter/impeller/renderer/BUILD.gn index fa27c57d1e9e5..73fb979e07c13 100644 --- a/engine/src/flutter/impeller/renderer/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/BUILD.gn @@ -98,6 +98,7 @@ impeller_component("renderer") { deps = [ "//flutter/fml", + "//third_party/abseil-cpp/absl/container:linked_hash_map", "//third_party/abseil-cpp/absl/strings", ] } diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h index 901ac76977431..97940df514c8b 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h @@ -5,12 +5,11 @@ #ifndef FLUTTER_IMPELLER_RENDERER_PIPELINE_COMPILE_QUEUE_H_ #define FLUTTER_IMPELLER_RENDERER_PIPELINE_COMPILE_QUEUE_H_ -#include - #include "flutter/fml/closure.h" #include "flutter/fml/concurrent_message_loop.h" #include "impeller/base/thread.h" #include "impeller/renderer/pipeline_descriptor.h" +#include "third_party/abseil-cpp/absl/container/linked_hash_map.h" namespace impeller { @@ -125,10 +124,10 @@ class PipelineCompileQueue private: Mutex pending_jobs_mutex_; - std::unordered_map, - ComparableEqual> + absl::linked_hash_map, + ComparableEqual> pending_jobs_ IPLR_GUARDED_BY(pending_jobs_mutex_); size_t priorities_elevated_ = {}; fml::closure TakeJob(const PipelineDescriptor& desc); diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc index 14be51dcd1556..9d9e0611b8f7a 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc @@ -27,6 +27,8 @@ class TestPipelineCompileQueue : public PipelineCompileQueue { } bool HasPendingJobsForTest() { return HasPendingJobs(); } + + void DoOneJobForTest() { DoOneJob(); } }; TEST(PipelineCompileQueueTest, AddJobReturnsTrueForNewDescriptor) { @@ -92,5 +94,27 @@ TEST(PipelineCompileQueueTest, FinishAllJobsDrainsQueue) { EXPECT_TRUE(job_executed); } +TEST(PipelineCompileQueueTest, ExecutesJobsInInsertionOrder) { + constexpr size_t kJobCount = 10; + auto queue = std::make_shared(); + + std::vector job_order; + for (size_t i = 0; i < kJobCount; i++) { + PipelineDescriptor desc; + desc.SetLabel(std::to_string(i)); + ASSERT_TRUE(queue->AddJobForTest( + desc, [&job_order, index = i] { job_order.push_back(index); })); + } + + for (size_t i = 0; i < kJobCount; i++) { + queue->DoOneJobForTest(); + } + + EXPECT_EQ(job_order.size(), kJobCount); + for (size_t i = 0; i < kJobCount; i++) { + EXPECT_EQ(i, job_order[i]); + } +} + } // namespace testing } // namespace impeller From fc532802804a9f0c8ec06b58bd90c722d8b0ca85 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 31 Jul 2026 12:20:59 -0400 Subject: [PATCH 146/147] Roll Skia from 7ef86a5b0eb9 to f73c4510d12d (1 revision) (#190352) https://skia.googlesource.com/skia.git/+log/7ef86a5b0eb9..f73c4510d12d 2026-07-31 michaelludwig@google.com [graphite] Track pipeline match and cluster draws by pipeline in Layer If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC aaclarke@google.com,bwils@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 1055e26f7a2e6..1666d37147f40 100644 --- a/DEPS +++ b/DEPS @@ -16,7 +16,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'dart_ai_rev': '9c96bfe5f091c9451eff5b59c9bffeb2e806b875', - 'skia_revision': '7ef86a5b0eb91db85580bd71ebed3c4ba489d5ba', + 'skia_revision': 'f73c4510d12da77512253d3a00d72275b45427c7', # Do not download the Emscripten SDK by default. # This prevents us from downloading the Emscripten toolchain for builds From 3240d4a01482ee0272d083bb355131c438fea4b9 Mon Sep 17 00:00:00 2001 From: reidbaker-agent Date: Wed, 22 Jul 2026 01:08:25 +0000 Subject: [PATCH 147/147] Add AGP public-API migration docs (contributor doc + breaking-change page draft) Phase P0 of the Flutter Gradle Plugin migration to the AGP public API surface (flutter/flutter#180137, flutter/flutter#166550): the contributor migration record (replacement map, decision records, phase map, revert-window table) and the draft of the user-facing breaking-change page to be published to docs.flutter.dev before the newDsl flip reaches beta. Revert-safe: always. --- ...Flutter-Gradle-Plugin-to-AGP-public-API.md | 205 ++++++++++++++++++ docs/platforms/android/website-page-draft.md | 191 ++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 docs/platforms/android/Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md create mode 100644 docs/platforms/android/website-page-draft.md diff --git a/docs/platforms/android/Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md b/docs/platforms/android/Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md new file mode 100644 index 0000000000000..43a37eea10412 --- /dev/null +++ b/docs/platforms/android/Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md @@ -0,0 +1,205 @@ +# Migrating the Flutter Gradle Plugin to the AGP Public API Surface + +This document is the contributor-facing record of the migration of the Flutter +Gradle Plugin (FGP) off the legacy Android Gradle Plugin (AGP) DSL/Variant API +and AGP internals, onto the public API surface shipped in the +`com.android.tools.build:gradle-api` artifact. + +Umbrella issues: + +- newDsl flip: https://github.com/flutter/flutter/issues/180137 +- Variant API migration: https://github.com/flutter/flutter/issues/166550 + +The user-facing breaking-change page draft lives next to this file in +[`website-page-draft.md`](website-page-draft.md). It must be published to +`docs.flutter.dev/release/breaking-changes/` before the newDsl flip (phase P9) +reaches the beta channel. + +## Why + +AGP 9 (January 2026) deprecated the old DSL and Variant APIs behind the +`android.newDsl=false` escape hatch. AGP 10 (late 2026) removes those APIs +entirely **and** removes access to AGP internals — only the public surface of +the `gradle-api` artifact remains. Today the FGP: + +- compiles against the FULL `com.android.tools.build:gradle` artifact + (`packages/flutter_tools/gradle/build.gradle.kts`); +- uses the legacy variant API (`applicationVariants`, `libraryVariants`, + `variant.outputs`, `assembleProvider`, `packageApplicationProvider`, + `versionCodeOverride`); +- uses the legacy `BaseExtension` + (`FlutterPluginUtils.getLegacyAndroidExtension`); +- imports one internal DSL class (`com.android.build.gradle.internal.dsl.BuildType` + in `plugins/PluginHandler.kt`); +- imports one internal utility + (`com.android.build.gradle.internal.utils.getKotlinAndroidPluginVersion` in + `VersionFetcher.kt`); +- drives `flutter build aar` with legacy dynamic Groovy in + `aar_init_script.gradle`. + +Flutter templates pin AGP 9.1.0 but ship `android.newDsl=false`, and a tool +migrator (`disable_new_dsl_migration.dart`) adds the opt-out to existing +projects. That opt-out dies with AGP 10. + +## End state + +- The FGP uses only public APIs and compiles against `gradle-api`. +- Templates no longer ship `android.newDsl=false`. +- The opt-out **add** migrator is replaced by a **removal** migrator that + deletes only the Flutter-added opt-out lines. +- A fresh `flutter create` app builds with newDsl on. + +## Decision records + +1. **Min AGP floor: out of scope.** A separate in-flight version bump owns the + floor; this migration builds on whatever floor is in effect at landing. + Every replacement API used here was verified public in `gradle-api:8.11.1` + (decompiled jar inspection). If implementation finds a replacement API that + genuinely requires a higher min AGP: document which API and why no + compatible alternative exists in this file, then bump — otherwise version + floors are untouched by this work. +2. **"Public in 8.x" does not mean binary-compatible on 9.x.** + `AgpCommonExtensionWrapper.kt` exists precisely because the public + `CommonExtension` broke between AGP 8 and 9. Mitigation: a CI/test axis + compiling the FGP against gradle-api 9.x is mandatory from phase P2 onward, + plus a bytecode check (javap grep) that no compiled FGP class references + `CommonExtension` as an owner. +3. **`android.builtInKotlin=false` stays out of scope.** Flipping it requires + the separate built-in-Kotlin migration workstream. Users get a second + (smaller) gradle.properties churn later; the breaking-change page states + this explicitly. Corollary: the P9 removal migrator must anchor on the + `android.newDsl` property line — never on marker-comment wording alone — + because the template's builtInKotlin marker comment is nearly identical. +4. **Per-ABI versionCode mechanism.** Do NOT re-implement AGP's flavor-merge + precedence via a `finalizeDsl` snapshot. Preferred mechanism (spiked first + in P6): read-then-set on `VariantOutput.versionCode` inside `onVariants` — + it is seeded with the merged value; set `abiOffset * 1000 + current`, + avoiding a self-referential `.map`. Fall back to a snapshot only if + read-then-set is impossible; record the outcome here. + - *Spike result:* _pending (P6)_. +5. **`buildModeFor` semantics.** Every variant-scope call uses the + `(name, debuggable)` overload with the public `Component.debuggable`. + Name-based inference is confined to the one DSL-scope case with no public + signal (the library-plugin build-type copy in `PluginHandler`). This + preserves add-to-app custom-debuggable matching (a host `staging` + debuggable build type maps to debug engine artifacts). + +## Replacement map + +| Legacy usage | Where | Public replacement | Phase | +| --- | --- | --- | --- | +| `internal.utils.getKotlinAndroidPluginVersion` | `VersionFetcher.kt` | delete; rely on existing fallback chain (`kotlin_version` property → `KotlinAndroidPluginWrapper.pluginVersion` → reflection); null when KGP absent is OK | P1 | +| `compileSdkVersion` string compare (`"android-NN"` substring) | `FlutterPluginUtils.getCompileSdkFromProject`, `PluginHandler` warning | wrapper `compileSdk` / `compileSdkPreview`; numeric compare with defined preview semantics | P1 | +| `BaseExtension.ndkVersion` | `FlutterPluginUtils.getConfiguredNdkVersion` | wrapper `ndkVersion` | P1 | +| `buildModeFor(BuildType)` (legacy model type) | `FlutterPluginUtils.kt` | `buildModeFor(name, debuggable)` overload | P2 | +| `getLegacyAndroidExtension(project).buildTypes` loops | `PluginHandler.kt` | wrapper new-DSL `buildTypes` container | P2 | +| `internal.dsl.BuildType` live aliasing into plugin projects | `PluginHandler.kt` | `initWith`-based copy on new-DSL `BuildType`; app-specific props only when both sides are `ApplicationBuildType` | P3 | +| `BaseExtension` / `getLegacyAndroidExtension` (remaining call sites) | `FlutterPluginUtils.kt` | wrapper accessors incl. `externalNativeBuild` | P4 | +| eager `applicationVariants.configureEach` task creation; mergeAssets/processResources hooks | `FlutterPlugin.kt`, `FlutterPluginUtils.kt` | consolidated `onVariants` block; `CopyFlutterAssetsTask` + `variant.sources.assets.addGeneratedSourceDirectory` | P5 | +| `variant.outputs` + `packageApplicationProvider` + `doLast` APK copy; `versionCodeOverride` | `FlutterPluginUtils.kt` | `CopyFlutterApksTask` (`SingleArtifact.APK` + `BuiltArtifactsLoader`); read-then-set `VariantOutput.versionCode` | P6 | +| `libraryVariants.all` × host `applicationVariants.all` cross-wiring | `FlutterPlugin.kt` (add-to-app) | library-side `onVariants` with `Component.debuggable`; no host-project lookup | P7 | +| dynamic Groovy legacy API in `aar_init_script.gradle` | `aar_init_script.gradle` | `components`-based enumeration; ext-property guard | P8 | +| `android.newDsl=false` template/migrator | templates, `disable_new_dsl_migration.dart` | drop from templates; `RemoveNewDslOptOutMigration` | P9 | +| FULL `gradle` artifact dependency | `build.gradle.kts` | `gradle-api` artifact (compile-time proof of zero internal usage) | P10 | + +## Phase map + +Each phase is one PR-sized change on its own branch. P8 is an independent lane +(Groovy script, disjoint files); P0/P1 are disjoint from each other; everything +else serializes through `FlutterPlugin.kt` / `FlutterPluginUtils.kt`. + +| Phase | Branch | Size | Summary | +| --- | --- | --- | --- | +| P0 | `agp-api-doc` | S | this doc + website page draft | +| P1 | `agp-internal-utils` | S | VersionFetcher internal util removal; numeric compileSdk compare; ndkVersion via wrapper | +| P2 | `agp-buildmode-deps` | M | `buildModeFor` overloads; new-DSL flutter dependencies; 9.x compile axis | +| P3 | `agp-plugin-buildtypes` | M | `initWith` copy for plugin build types; drop internal import; internal-import lint | +| P4 | `agp-ndk-fallback` | S | delete `BaseExtension`; externalNativeBuild via wrapper | +| P5 | `agp-assets-onvariants` | L | lazy task registration (5a) + generated-asset-dir wiring (5b) | +| P6 | `agp-apk-copy-versioncode` | L | `CopyFlutterApksTask`; per-ABI versionCode; app path legacy-free | +| P7 | `agp-add-to-app` | L | library-side `onVariants`; delete host cross-wiring + P5a legacy fork | +| P8 | `agp-aar-script` | M | aar_init_script public-API cleanup | +| P9 | `agp-newdsl-flip` | M | templates drop opt-out; removal migrator; new error handlers | +| P10 | `agp-gradle-api` | M | dependency swap to `gradle-api`; test migration | + +## Cross-cutting rules + +- **R1 Lockstep:** any PR changing FGP-emitted message text updates the + matching `gradle_errors.dart` matcher and its Dart test in the same PR. +- **R2 Revert notes:** each PR description carries "revert-safe until phase X + lands"; once superseded, policy is fix-forward. At least one full post-submit + CI soak between dependent phases (no same-day stacking of P2–P4). +- **R3 9.x axis:** from P2, gradle unit tests additionally compile against + gradle-api 9.x in CI, plus the javap `CommonExtension` bytecode check. +- **R4 Config-cache:** master baseline established first; the per-phase + assertion is "no NEW config-cache violations", not full reuse. +- **R5 Internal-import lint:** once P3 lands, a checked-in test forbids + `com.android.build.gradle.internal.*` imports in `src/main`. +- **R6 Staged newDsl=true axis:** app flows green from end of P6; add-to-app + from P7; aar from P8. The full matrix is the P9 gate. + +## Revert-window table + +| Phase | Revert window | +| --- | --- | +| P0 | always revert-safe | +| P1–P4 | each until the next phase in the chain lands; then fix-forward | +| P5 | until P6 lands | +| P6 / P7 | mutually tolerant (disjoint app/module paths) until P10 | +| P8 | revert-safe even after P10, but not after P9 | +| P9 | cleanly revertible in isolation | +| P10 | cleanly revertible in isolation | + +## Features that must break (tracked; updated as implementation learns) + +1. **User build scripts using legacy APIs** (`applicationVariants.all` + APK-rename recipes) fail under newDsl — the biggest break. Mitigated by new + error handlers (P9) and the website page. +2. **flutter-apk copy**: same names/paths (`app[-abi][-flavor]-.apk`, + byte-matching the current concatenation order), but an UP-TO-DATE-capable + finalizer task replaces the `doLast` block; new task names appear in + `gradlew tasks`. +3. **Per-ABI versionCode**: post-`finalizeDsl` user mutations (`afterEvaluate` + CI patterns) may behave differently; a runtime divergence warning is added. +4. **Custom build types → plugins**: live-aliased instances become `initWith` + copies; library plugins cannot receive `isDebuggable` (no public setter on + `LibraryBuildType`) — plugin-side `BuildConfig.DEBUG`/JNI debuggability may + differ for custom debuggable build types; matching preserved via + `matchingFallbacks`. +5. **Asset merge**: flutter assets become a merged source dir instead of a + post-merge overwrite; collisions resolve by AGP source-set priority. +6. **Add-to-app**: the explicit `:app:mergeAssets.dependsOn` edge and + host-project lookup are removed; `flutter.hostAppProjectName` becomes a + no-op with a deprecation warning naming a removal milestone; ordering + against `copyFlutterAssets` task names may break. +7. **Task realization/type**: flutter tasks become lazy `TaskProvider`s, and + `copyFlutterAssets` changes type from `org.gradle.api.tasks.Copy` to a + custom task class — `tasks.named(..., Copy::class)` casts fail. +8. **`flutter build aar`**: the singleVariant dedup guard becomes an + ext-property/try-catch with a specified error message; variant enumeration + moves from `libraryVariants` to `components` — partial user `singleVariant` + declarations surface differently. +9. **newDsl flip**: new projects lose the opt-out; the removal migrator deletes + only marker-tagged `android.newDsl` lines (template marker "This newDsl flag + was added by the Flutter template"; migrator marker "This newDsl flag was + added automatically by Flutter migrator"), anchored on the property line so + the adjacent builtInKotlin lines are never touched; hand-added opt-outs are + respected. +10. **compileSdk mismatch warning** becomes a numeric compare with defined + preview-vs-numeric semantics; the message keeps a distinctive substring of + the old phrasing for searchability. + +## Verification matrix + +Full matrix at P6, P7, P9, P10; targeted per-phase otherwise. + +1. `cd packages/flutter_tools/gradle && ./gradlew test` (+ the R3 9.x axis) +2. Targeted `integration.shard` tests named in each phase +3. Scratch-app matrix: apk/appbundle × 3 modes; `--flavor`; `--split-per-abi` + (+ apkanalyzer versionCode assertions, including the + flavor-defined-versionCode case); `--deferred-components`; plugin with a + custom build type; `flutter build aar`; add-to-app source & AAR host flows; + `flutter run` / hot restart / `flutter attach`; Windows smoke for the copy + tasks +4. AGP axis: current floor AND 9.1 + `newDsl=false`; staged `newDsl=true` per R6 +5. Config-cache per R4 diff --git a/docs/platforms/android/website-page-draft.md b/docs/platforms/android/website-page-draft.md new file mode 100644 index 0000000000000..de89c1b5ba0c5 --- /dev/null +++ b/docs/platforms/android/website-page-draft.md @@ -0,0 +1,191 @@ +# Android builds use the new Android Gradle Plugin DSL and Variant APIs + +*Draft breaking-change page for `docs.flutter.dev/release/breaking-changes/`. +This file is the source of truth until the page is published to +flutter/website; publishing must complete before the newDsl flip reaches the +beta channel. Contributor-facing details live in +[Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md](Migrating-Flutter-Gradle-Plugin-to-AGP-public-API.md).* + +## Summary + +The Flutter Gradle Plugin now uses only the public Android Gradle Plugin (AGP) +API, and new and migrated Flutter projects build with AGP's new DSL enabled +(`android.newDsl` is no longer set to `false` by Flutter). Gradle build +scripts that use the legacy AGP APIs — most commonly +`android.applicationVariants` — fail to configure and must be migrated to the +AGP Variant API. + +## Background + +AGP 9 deprecated the legacy DSL and Variant APIs behind the +`android.newDsl=false` flag. AGP 10 removes them entirely. Flutter previously +added `android.newDsl=false` to your `gradle.properties` (via the project +templates and an automatic migration) to keep legacy builds working. That +opt-out stops working with AGP 10, so Flutter has migrated its own Gradle +plugin to the public API and removed the opt-out from templates. A migration +now *removes* the opt-out lines that Flutter previously added — it only touches +lines carrying Flutter's marker comments, and prints a message when it does. +Opt-outs you added by hand are left alone. + +`android.builtInKotlin=false` is **not** affected by this change. It is owned +by the separate built-in-Kotlin migration (tracked in ), which means one more (smaller) +`gradle.properties` change later. + +## Migration guide + +### Renaming APKs (`applicationVariants.all`) + +Before: + +```groovy +android { + applicationVariants.all { variant -> + variant.outputs.all { output -> + outputFileName = "myapp-${variant.versionName}.apk" + } + } +} +``` + +After (Variant API, `build.gradle` / `build.gradle.kts`): + +```kotlin +androidComponents { + onVariants(selector().all()) { variant -> + variant.outputs.forEach { output -> + // Use variant.name / output.filters and your own naming scheme. + } + } +} +``` + +For output *file* renames, prefer consuming the built APKs from +`SingleArtifact.APK` with a task wired through +`variant.artifacts.use(...)`, or copy/rename in a finalizer task. Flutter's +own copy step already places APKs at +`build/app/outputs/flutter-apk/app[-abi][-flavor]-.apk` with unchanged +names and paths. + +### Setting per-ABI or per-variant versionCode + +Before: + +```groovy +android.applicationVariants.all { variant -> + variant.outputs.each { output -> + output.versionCodeOverride = abiCodes.get(output.getFilter(OutputFile.ABI)) * 1000 + variant.versionCode + } +} +``` + +After: + +```kotlin +androidComponents { + onVariants(selector().all()) { variant -> + variant.outputs.forEach { output -> + val abi = output.filters.find { it.filterType == FilterConfiguration.FilterType.ABI }?.identifier + val base = output.versionCode.get() ?: 1 + output.versionCode.set((abiCodes[abi] ?: 0) * 1000 + base) + } + } +} +``` + +Note: Flutter itself sets per-ABI version codes for `--split-per-abi` inside +`onVariants`. If your CI mutates version codes in `afterEvaluate`, that runs at +a different time than before; Flutter prints a warning when it detects a +divergence between the DSL value and the final output value. + +### Custom build types and plugins + +Flutter copies your app's custom build types onto Flutter plugin projects so +they resolve. With the new DSL these are `initWith` copies rather than live +aliases: + +- Set `matchingFallbacks` on custom build types so dependent Android libraries + resolve, for example: + + ```kotlin + android { + buildTypes { + create("staging") { + initWith(getByName("debug")) + matchingFallbacks += listOf("debug", "release") + } + } + } + ``` + +- Library (plugin) projects cannot be marked debuggable through the public + API, so a plugin's `BuildConfig.DEBUG` and native (JNI) debuggability can + differ from before for custom *debuggable* build types. Variant matching + still works via `matchingFallbacks`. + +### Add-to-app (Flutter module in a host app) + +- Flutter no longer looks up or configures the host `:app` project from the + module. The dependency between your host's asset merging and Flutter's asset + copy is expressed through the Variant API instead of an explicit + `mergeAssets.dependsOn(...)` edge. Build scripts that reference + Flutter's `copyFlutterAssets` tasks by name or type may break: the + tasks are now registered lazily and are no longer of type + `org.gradle.api.tasks.Copy`. +- `flutter.hostAppProjectName` in `gradle.properties` is now a no-op. Flutter + prints a deprecation warning naming the removal milestone. It was only used + for the host-project lookup, which no longer exists. +- Flutter maps host build types to Flutter build modes using the public + "debuggable" flag: `profile` stays `profile`, debuggable build types map to + `debug`, everything else maps to `release`. If your host has no `profile` + build type, add `matchingFallbacks`: + + ```kotlin + create("staging") { + initWith(getByName("debug")) + isDebuggable = true // staging gets debug Flutter artifacts + matchingFallbacks += listOf("debug", "release") + } + ``` + +### Flutter plugin authors + +- Do not read `android.applicationVariants` / `android.libraryVariants` in + plugin build scripts; use `androidComponents.onVariants`. +- Do not assume Flutter's tasks exist at configuration time or have specific + types; look tasks up lazily (`tasks.named`) without a type, or better, wire + through Variant API artifacts. +- Test your plugin's example app with AGP 9+ **without** `android.newDsl=false`. + +### `flutter build aar` + +Variant enumeration for AAR builds now uses the public `components` API. If +your module's build script declares `singleVariant(...)` publishing itself, +Flutter detects the overlap and reports it with an actionable error instead of +failing inside AGP. + +## Escape hatch (temporary) + +If you cannot migrate immediately, add the opt-out by hand to +`android/gradle.properties`: + +```properties +android.newDsl=false +``` + +**This stops working with AGP 10** (removal of the legacy APIs). Treat it as a +short-term unblock only; hand-added opt-outs are never touched by Flutter's +migrator. + +## Timeline + +Landed in version: TBD
+In stable release: TBD + +## References + +- AGP 9 release notes (new DSL): + https://developer.android.com/build/releases/agp-9-0-0-release-notes +- Flutter umbrella issues: + [flutter/flutter#180137](https://github.com/flutter/flutter/issues/180137), + [flutter/flutter#166550](https://github.com/flutter/flutter/issues/166550)