diff --git a/dev/automated_tests/pubspec.yaml b/dev/automated_tests/pubspec.yaml index 3e1070e7fd8..ee056f0d990 100644 --- a/dev/automated_tests/pubspec.yaml +++ b/dev/automated_tests/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -73,4 +73,4 @@ flutter: assets: - icon/ -# PUBSPEC CHECKSUM: 43e1 +# PUBSPEC CHECKSUM: 7ae2 diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index 1e28cf0e760..d1ec39edb20 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -92,4 +92,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: 6a4f +# PUBSPEC CHECKSUM: a450 diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index 97ee0b121ab..ec8e251b0b9 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -93,4 +93,4 @@ flutter: - packages/flutter_gallery_assets/food/cherry_pie.png - assets/999x1000.png -# PUBSPEC CHECKSUM: 6a4f +# PUBSPEC CHECKSUM: a450 diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml index 5e56e46bb2d..a3f0c5c7a31 100644 --- a/dev/benchmarks/microbenchmarks/pubspec.yaml +++ b/dev/benchmarks/microbenchmarks/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dart_style: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -78,4 +78,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 053c +# PUBSPEC CHECKSUM: c23d diff --git a/dev/benchmarks/test_apps/stocks/pubspec.yaml b/dev/benchmarks/test_apps/stocks/pubspec.yaml index 019864e08d3..2bc04e283e8 100644 --- a/dev/benchmarks/test_apps/stocks/pubspec.yaml +++ b/dev/benchmarks/test_apps/stocks/pubspec.yaml @@ -53,7 +53,7 @@ dev_dependencies: archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -87,4 +87,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 5272 +# PUBSPEC CHECKSUM: bc73 diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index 1e88c8a3341..111771c7b46 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -69,4 +69,4 @@ dev_dependencies: mockito: 4.1.1 test_api: 0.2.11 -# PUBSPEC CHECKSUM: 26ab +# PUBSPEC CHECKSUM: abac diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index d8c3172b635..19aa8d8685a 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -76,4 +76,4 @@ dev_dependencies: watcher: 0.9.7+13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: d160 +# PUBSPEC CHECKSUM: 8b61 diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index 2e5756b3c0a..10e58c8a931 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,4 +70,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml index a495c4fec73..0d5e70ba699 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml +++ b/dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -139,4 +139,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: c1f5 +# PUBSPEC CHECKSUM: e9f6 diff --git a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml index 05ea6635991..c8d3288c5e1 100644 --- a/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml +++ b/dev/integration_tests/android_splash_screens/splash_screen_trans_rotate/pubspec.yaml @@ -50,7 +50,7 @@ dev_dependencies: boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -139,4 +139,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: c1f5 +# PUBSPEC CHECKSUM: e9f6 diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml index 82c0d64ef26..c423cc307db 100644 --- a/dev/integration_tests/android_views/pubspec.yaml +++ b/dev/integration_tests/android_views/pubspec.yaml @@ -48,7 +48,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -87,4 +87,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: a1cf +# PUBSPEC CHECKSUM: e3d0 diff --git a/dev/integration_tests/channels/pubspec.yaml b/dev/integration_tests/channels/pubspec.yaml index f354b0c790e..5035a8cb631 100644 --- a/dev/integration_tests/channels/pubspec.yaml +++ b/dev/integration_tests/channels/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/codegen/pubspec.yaml b/dev/integration_tests/codegen/pubspec.yaml index eab661b3d6b..185525f982d 100644 --- a/dev/integration_tests/codegen/pubspec.yaml +++ b/dev/integration_tests/codegen/pubspec.yaml @@ -42,7 +42,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -81,4 +81,4 @@ builders: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/external_ui/pubspec.yaml b/dev/integration_tests/external_ui/pubspec.yaml index b1807e503c1..c1e9202ae5d 100644 --- a/dev/integration_tests/external_ui/pubspec.yaml +++ b/dev/integration_tests/external_ui/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/flavors/pubspec.yaml b/dev/integration_tests/flavors/pubspec.yaml index 2cc50bc32eb..65c64b9c3fa 100644 --- a/dev/integration_tests/flavors/pubspec.yaml +++ b/dev/integration_tests/flavors/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml index 40e2f5bca89..2e2440c9682 100644 --- a/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml +++ b/dev/integration_tests/flutter_driver_screenshot_test/pubspec.yaml @@ -43,7 +43,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +80,4 @@ flutter: assets: - assets/ -# PUBSPEC CHECKSUM: ec3a +# PUBSPEC CHECKSUM: d23b diff --git a/dev/integration_tests/image_loading/pubspec.yaml b/dev/integration_tests/image_loading/pubspec.yaml index 71e33bc16b6..3f1598f292c 100644 --- a/dev/integration_tests/image_loading/pubspec.yaml +++ b/dev/integration_tests/image_loading/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,4 +62,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: f8e0 +# PUBSPEC CHECKSUM: e1e1 diff --git a/dev/integration_tests/platform_interaction/pubspec.yaml b/dev/integration_tests/platform_interaction/pubspec.yaml index b36f4c23e13..8c8154994a9 100644 --- a/dev/integration_tests/platform_interaction/pubspec.yaml +++ b/dev/integration_tests/platform_interaction/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 7669 +# PUBSPEC CHECKSUM: 086a diff --git a/dev/integration_tests/release_smoke_test/pubspec.yaml b/dev/integration_tests/release_smoke_test/pubspec.yaml index ca59315ff0e..68e1045036a 100644 --- a/dev/integration_tests/release_smoke_test/pubspec.yaml +++ b/dev/integration_tests/release_smoke_test/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: flutter_test: sdk: flutter - e2e: 0.2.3 + e2e: 0.2.3+1 archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -40,4 +40,4 @@ dev_dependencies: test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: b7d2 +# PUBSPEC CHECKSUM: bb2f diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index ad747568b1d..9d00f125313 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -83,4 +83,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4450 +# PUBSPEC CHECKSUM: 4051 diff --git a/dev/snippets/pubspec.yaml b/dev/snippets/pubspec.yaml index 2901c86d51c..c79e4b92036 100644 --- a/dev/snippets/pubspec.yaml +++ b/dev/snippets/pubspec.yaml @@ -46,7 +46,7 @@ dev_dependencies: test: 1.9.4 boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http: 0.12.0+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -100,4 +100,4 @@ executables: vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 59ea +# PUBSPEC CHECKSUM: 42eb diff --git a/dev/tools/pubspec.yaml b/dev/tools/pubspec.yaml index 882b3178f91..4fa348ec205 100644 --- a/dev/tools/pubspec.yaml +++ b/dev/tools/pubspec.yaml @@ -36,7 +36,7 @@ dev_dependencies: _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,4 +68,4 @@ dev_dependencies: web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: fbaa +# PUBSPEC CHECKSUM: 2bab diff --git a/examples/catalog/pubspec.yaml b/examples/catalog/pubspec.yaml index 30212e63f22..fc019512e70 100644 --- a/examples/catalog/pubspec.yaml +++ b/examples/catalog/pubspec.yaml @@ -30,7 +30,7 @@ dev_dependencies: boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -83,4 +83,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4450 +# PUBSPEC CHECKSUM: 4051 diff --git a/examples/flutter_gallery/pubspec.yaml b/examples/flutter_gallery/pubspec.yaml index 124e0907453..58d6de1805c 100644 --- a/examples/flutter_gallery/pubspec.yaml +++ b/examples/flutter_gallery/pubspec.yaml @@ -52,7 +52,7 @@ dev_dependencies: async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -271,4 +271,4 @@ flutter: - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf -# PUBSPEC CHECKSUM: 5f11 +# PUBSPEC CHECKSUM: 3212 diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index dfc0f595598..2fac3c98b43 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -28,7 +28,7 @@ dev_dependencies: boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -82,4 +82,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4450 +# PUBSPEC CHECKSUM: 4051 diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 7d62d9e9fb8..c84fc0adcb1 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -28,7 +28,7 @@ dev_dependencies: boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - coverage: 0.13.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -82,4 +82,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4450 +# PUBSPEC CHECKSUM: 4051 diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart index 82068919169..c6343e502a3 100644 --- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart @@ -4,18 +4,14 @@ import 'dart:async'; -import 'package:build_daemon/client.dart'; -import 'package:dwds/dwds.dart'; import 'package:meta/meta.dart'; import 'package:vm_service/vm_service.dart' as vmservice; import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' hide StackTrace; import '../application_package.dart'; -import '../base/async_guard.dart'; import '../base/common.dart'; import '../base/file_system.dart'; -import '../base/io.dart'; import '../base/logger.dart'; import '../base/net.dart'; import '../base/terminal.dart'; @@ -34,10 +30,10 @@ import '../reporting/reporting.dart'; import '../resident_runner.dart'; import '../run_hot.dart'; import '../web/chrome.dart'; +import '../web/compile.dart'; import '../web/devfs_web.dart'; import '../web/web_device.dart'; import '../web/web_runner.dart'; -import 'web_fs.dart'; /// Injectable factory to create a [ResidentWebRunner]. class DwdsWebRunnerFactory extends WebRunnerFactory { @@ -52,19 +48,7 @@ class DwdsWebRunnerFactory extends WebRunnerFactory { @required List dartDefines, @required UrlTunneller urlTunneller, }) { - if (featureFlags.isWebIncrementalCompilerEnabled && debuggingOptions.buildInfo.isDebug) { - return _ExperimentalResidentWebRunner( - device, - target: target, - flutterProject: flutterProject, - debuggingOptions: debuggingOptions, - ipv6: ipv6, - stayResident: stayResident, - dartDefines: dartDefines, - // TODO(dantup): If this becomes default it may need to urlTunneller. - ); - } - return _DwdsResidentWebRunner( + return _ResidentWebRunner( device, target: target, flutterProject: flutterProject, @@ -116,7 +100,6 @@ abstract class ResidentWebRunner extends ResidentRunner { bool get _enableDwds => debuggingEnabled; - WebFs _webFs; ConnectionResult _connectionResult; StreamSubscription _stdOutSub; bool _exited = false; @@ -155,9 +138,15 @@ abstract class ResidentWebRunner extends ResidentRunner { return; } await _stdOutSub?.cancel(); - await _webFs?.stop(); await device.device.stopApp(null); - _generatedEntrypointDirectory?.deleteSync(recursive: true); + try { + _generatedEntrypointDirectory?.deleteSync(recursive: true); + } on FileSystemException { + // Best effort to clean up temp dirs. + globals.printTrace( + 'Failed to clean up temp directory: ${_generatedEntrypointDirectory.path}', + ); + } if (ChromeLauncher.hasChromeInstance) { final Chrome chrome = await ChromeLauncher.connectedInstance; await chrome.close(); @@ -348,8 +337,8 @@ abstract class ResidentWebRunner extends ResidentRunner { } } -class _ExperimentalResidentWebRunner extends ResidentWebRunner { - _ExperimentalResidentWebRunner( +class _ResidentWebRunner extends ResidentWebRunner { + _ResidentWebRunner( FlutterDevice device, { String target, @required FlutterProject flutterProject, @@ -357,6 +346,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { @required DebuggingOptions debuggingOptions, bool stayResident = true, @required List dartDefines, + @required this.urlTunneller, }) : super( device, flutterProject: flutterProject, @@ -367,8 +357,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { dartDefines: dartDefines, ); - @override - bool get debuggingEnabled => false; + final UrlTunneller urlTunneller; @override Future run({ @@ -405,13 +394,31 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { ? await globals.os.findFreePort() : int.tryParse(debuggingOptions.port); device.devFS = WebDevFS( - effectiveHostname, - hostPort, - packagesFilePath, + hostname: effectiveHostname, + port: hostPort, + packagesFilePath: packagesFilePath, + urlTunneller: urlTunneller, + buildMode: debuggingOptions.buildInfo.mode, + enableDwds: _enableDwds, ); final Uri url = await device.devFS.create(); - await _updateDevFS(fullRestart: true); - device.generator.accept(); + if (debuggingOptions.buildInfo.isDebug) { + final UpdateFSReport report = await _updateDevFS(fullRestart: true); + if (!report.success) { + globals.printError('Failed to compile application.'); + return 1; + } + device.generator.accept(); + } else { + await buildWeb( + flutterProject, + target, + debuggingOptions.buildInfo, + debuggingOptions.initializePlatform, + dartDefines, + false, + ); + } await device.device.startApp( package, mainPath: target, @@ -442,18 +449,39 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { progressId: 'hot.restart', ); - final UpdateFSReport report = await _updateDevFS(fullRestart: fullRestart); - if (report.success) { - device.generator.accept(); + String reloadModules; + if (debuggingOptions.buildInfo.isDebug) { + // Full restart is always false for web, since the extra recompile is wasteful. + final UpdateFSReport report = await _updateDevFS(fullRestart: false); + if (report.success) { + device.generator.accept(); + } else { + status.stop(); + await device.generator.reject(); + return OperationResult(1, 'Failed to recompile application.'); + } + reloadModules = report.invalidatedModules + .map((String module) => '"$module"') + .join(','); } else { - await device.generator.reject(); + try { + await buildWeb( + flutterProject, + target, + debuggingOptions.buildInfo, + debuggingOptions.initializePlatform, + dartDefines, + false, + ); + } on ToolExit { + return OperationResult(1, 'Failed to recompile application.'); + } } - final String modules = report.invalidatedModules - .map((String module) => '"$module"') - .join(','); try { - if (fullRestart || !debuggingOptions.buildInfo.isDebug) { + if (!deviceIsDebuggable) { + globals.printStatus('Recompile complete. Page requires refresh.'); + } else if (fullRestart || !debuggingOptions.buildInfo.isDebug) { // On non-debug builds, a hard refresh is required to ensure the // up to date sources are loaded. await _wipConnection?.sendCommand('Page.reload', { @@ -462,7 +490,7 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { } else { await _wipConnection?.debugger ?.sendCommand('Runtime.evaluate', params: { - 'expression': 'window.\$hotReloadHook([$modules])', + 'expression': 'window.\$hotReloadHook([$reloadModules])', 'awaitPromise': true, 'returnByValue': true, }); @@ -473,19 +501,23 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { } finally { status.stop(); } + final String verb = fullRestart ? 'Restarted' : 'Reloaded'; globals.printStatus('$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.'); - if (!fullRestart) { + + // Don't track restart times for dart2js builds or web-server devices. + if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) { flutterUsage.sendTiming('hot', 'web-incremental-restart', timer.elapsed); + HotEvent( + 'restart', + targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript), + sdkName: await device.device.sdkNameAndVersion, + emulator: false, + fullRestart: true, + reason: reason, + overallTimeInMs: timer.elapsed.inMilliseconds, + ).send(); } - HotEvent( - 'restart', - targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript), - sdkName: await device.device.sdkNameAndVersion, - emulator: false, - fullRestart: true, - reason: reason, - ).send(); return OperationResult.ok; } @@ -509,9 +541,10 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { .childFile('generated_plugin_registrant.dart') .absolute.path; final Uri generatedImport = packageUriMapper.map(generatedPath); + final String importedEntrypoint = packageUriMapper.map(main).toString() ?? 'file://$main'; final String entrypoint = [ - 'import "${packageUriMapper.map(main)}" as entrypoint;', + 'import "$importedEntrypoint" as entrypoint;', 'import "dart:ui" as ui;', if (hasWebPlugins) 'import "package:flutter_web_plugins/flutter_web_plugins.dart";', @@ -583,259 +616,13 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner { } _wipConnection = await chromeTab.connect(); } - appStartedCompleter?.complete(); - connectionInfoCompleter?.complete(DebugConnectionInfo()); - if (stayResident) { - await waitForAppToFinish(); - } else { - await stopEchoingDeviceLog(); - await exitApp(); - } - await cleanupAtFinish(); - return 0; - } -} - -class _DwdsResidentWebRunner extends ResidentWebRunner { - _DwdsResidentWebRunner( - FlutterDevice device, { - String target, - @required FlutterProject flutterProject, - @required bool ipv6, - @required DebuggingOptions debuggingOptions, - @required this.urlTunneller, - bool stayResident = true, - @required List dartDefines, - }) : super( - device, - flutterProject: flutterProject, - target: target ?? globals.fs.path.join('lib', 'main.dart'), - debuggingOptions: debuggingOptions, - ipv6: ipv6, - stayResident: stayResident, - dartDefines: dartDefines, - ); - - UrlTunneller urlTunneller; - - @override - Future run({ - Completer connectionInfoCompleter, - Completer appStartedCompleter, - String route, - }) async { - firstBuildTime = DateTime.now(); - final ApplicationPackage package = await ApplicationPackageFactory.instance.getPackageForPlatform( - TargetPlatform.web_javascript, - applicationBinary: null, - ); - if (package == null) { - globals.printError('This application is not configured to build on the web.'); - globals.printError('To add web support to a project, run `flutter create .`.'); - return 1; - } - if (!globals.fs.isFileSync(mainPath)) { - String message = 'Tried to run $mainPath, but that file does not exist.'; - if (target == null) { - message += - '\nConsider using the -t option to specify the Dart file to start.'; - } - globals.printError(message); - return 1; - } - final String modeName = debuggingOptions.buildInfo.friendlyModeName; - globals.printStatus( - 'Launching ${globals.fsUtils.getDisplayPath(target)} ' - 'on ${device.device.name} in $modeName mode...', - ); - Status buildStatus; - bool statusActive = false; - try { - // dwds does not handle uncaught exceptions from its servers. To work - // around this, we need to catch all uncaught exceptions and determine if - // they are fatal or not. - buildStatus = globals.logger.startProgress('Building application for the web...', timeout: null); - statusActive = true; - final int result = await asyncGuard(() async { - _webFs = await webFsFactory( - target: target, - flutterProject: flutterProject, - buildInfo: debuggingOptions.buildInfo, - initializePlatform: debuggingOptions.initializePlatform, - hostname: debuggingOptions.hostname, - port: debuggingOptions.port, - urlTunneller: urlTunneller, - skipDwds: !_enableDwds, - dartDefines: dartDefines, - ); - // When connecting to a browser, update the message with a seemsSlow notification - // to handle the case where we fail to connect. - buildStatus.stop(); - statusActive = false; - if (supportsServiceProtocol) { - buildStatus = globals.logger.startProgress( - 'Attempting to connect to browser instance..', - timeout: const Duration(seconds: 30), - ); - statusActive = true; - } - await device.device.startApp( - package, - mainPath: target, - debuggingOptions: debuggingOptions, - platformArgs: { - 'uri': _webFs.uri, - }, - ); - if (_enableDwds) { - final bool useDebugExtension = device.device is WebServerDevice && debuggingOptions.startPaused; - _connectionResult = await _webFs.connect(useDebugExtension); - unawaited(_connectionResult.debugConnection.onDone.whenComplete(_cleanupAndExit)); - } - if (statusActive) { - buildStatus.stop(); - statusActive = false; - } - appStartedCompleter?.complete(); - return attach( - connectionInfoCompleter: connectionInfoCompleter, - appStartedCompleter: appStartedCompleter, - ); - }); - return result; - } on VersionSkew { - // Thrown if an older build daemon is already running. - throwToolExit( - 'Another build daemon is already running with an older version.\n' - 'Try exiting other Flutter processes in this project and try again.'); - } on OptionsSkew { - // Thrown if a build daemon is already running with different configuration. - throwToolExit( - 'Another build daemon is already running with different configuration.\n' - 'Exit other Flutter processes running in this project and try again.'); - } on WebSocketException { - throwToolExit('Failed to connect to WebSocket.'); - } on BuildException { - throwToolExit('Failed to build application for the Web.'); - } on ChromeDebugException catch (err, stackTrace) { - throwToolExit( - 'Failed to establish connection with Chrome. Try running the application again.\n' - 'If this problem persists, please file an issue with the details below:\n$err\n$stackTrace'); - } on AppConnectionException { - throwToolExit( - 'Failed to establish connection with the application instance in Chrome.\n' - 'This can happen if the websocket connection used by the web tooling is ' - 'unabled to correctly establish a connection, for example due to a firewall.'); - } on MissingPortFile { - throwToolExit( - 'Failed to connect to build daemon.\nThe daemon either failed to ' - 'start or was killed by another process.'); - } on SocketException catch (err) { - throwToolExit(err.toString()); - } on StateError catch (err) { - final String message = err.toString(); - if (message.contains('Unable to start build daemon')) { - throwToolExit('Failed to start build daemon. The process might have ' - 'exited unexpectedly during startup. Try running the application ' - 'again.'); - } - rethrow; - } finally { - if (statusActive) { - buildStatus.stop(); - } - } - return 1; - } - - @override - Future restart({ - bool fullRestart = false, - bool pause = false, - String reason, - bool benchmarkMode = false, - }) async { - final Stopwatch timer = Stopwatch()..start(); - final Status status = globals.logger.startProgress( - 'Performing hot restart...', - timeout: supportsServiceProtocol - ? timeoutConfiguration.fastOperation - : timeoutConfiguration.slowOperation, - progressId: 'hot.restart', - ); - final bool success = await _webFs.recompile(); - if (!success) { - status.stop(); - return OperationResult(1, 'Failed to recompile application.'); - } - if (supportsServiceProtocol) { - // Send an event for only recompilation. - final Duration recompileDuration = timer.elapsed; - flutterUsage.sendTiming('hot', 'web-recompile', recompileDuration); - try { - final vmservice.Response reloadResponse = fullRestart - ? await _vmService.callServiceExtension('fullReload') - : await _vmService.callServiceExtension('hotRestart'); - final String verb = fullRestart ? 'Restarted' : 'Reloaded'; - globals.printStatus( - '$verb application in ${getElapsedAsMilliseconds(timer.elapsed)}.'); - - // Send timing analytics for full restart and for refresh. - final bool wasSuccessful = reloadResponse.type == 'Success'; - if (!wasSuccessful) { - return OperationResult(1, reloadResponse.toString()); - } - if (!fullRestart) { - flutterUsage.sendTiming('hot', 'web-restart', timer.elapsed); - flutterUsage.sendTiming('hot', 'web-refresh', timer.elapsed - recompileDuration); - } - return OperationResult.ok; - } on vmservice.RPCError { - return OperationResult(1, 'Page requires refresh.'); - } finally { - status.stop(); - HotEvent( - 'restart', - targetPlatform: getNameForTargetPlatform(TargetPlatform.web_javascript), - sdkName: await device.device.sdkNameAndVersion, - emulator: false, - fullRestart: true, - reason: reason, - ).send(); - } - } - // Allows browser refresh hot restart on non-debug builds. - if (device.device is ChromeDevice && !isRunningDebug) { - try { - final Chrome chrome = await ChromeLauncher.connectedInstance; - final ChromeTab chromeTab = await chrome.chromeConnection.getTab((ChromeTab chromeTab) { - return chromeTab.url.contains(debuggingOptions.hostname); - }); - final WipConnection wipConnection = await chromeTab.connect(); - // On non-debug builds, a hard refresh is required to ensure the - // up to date sources are loaded. - await wipConnection?.sendCommand('Page.reload', { - 'ignoreCache': !debuggingOptions.buildInfo.isDebug, - }); - status.stop(); - return OperationResult.ok; - } catch (err) { - globals.printTrace(err.toString()); - // Ignore error and continue with posted message; - } - } - status.stop(); - globals.printStatus('Recompile complete. Page requires refresh.'); - return OperationResult.ok; - } - - @override - Future attach({ - Completer connectionInfoCompleter, - Completer appStartedCompleter, - }) async { Uri websocketUri; if (supportsServiceProtocol) { + final WebDevFS webDevFS = device.devFS as WebDevFS; + final bool useDebugExtension = device.device is WebServerDevice && debuggingOptions.startPaused; + _connectionResult = await webDevFS.connect(useDebugExtension); + unawaited(_connectionResult.debugConnection.onDone.whenComplete(_cleanupAndExit)); + // Cleanup old subscriptions. These will throw if there isn't anything // listening, which is fine because that is what we want to ensure. try { @@ -855,6 +642,14 @@ class _DwdsResidentWebRunner extends ResidentWebRunner { globals.printStatus(message); }); unawaited(_vmService.registerService('reloadSources', 'FlutterTools')); + _vmService.registerServiceCallback('reloadSources', (Map params) async { + final bool pause = params['pause'] as bool ?? false; + await restart(benchmarkMode: false, pause: pause, fullRestart: false); + return {'type': 'Success'}; + }); + // Note: can't register our own hot restart hook. Would be fixed by moving + // to DWDS digests. + websocketUri = Uri.parse(_connectionResult.debugConnection.uri); // Always run main after connecting because start paused doesn't work yet. if (!debuggingOptions.startPaused || !supportsServiceProtocol) { @@ -873,8 +668,8 @@ class _DwdsResidentWebRunner extends ResidentWebRunner { if (websocketUri != null) { globals.printStatus('Debug service listening on $websocketUri'); } + appStartedCompleter?.complete(); connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri)); - if (stayResident) { await waitForAppToFinish(); } else { diff --git a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart index 52b740512b2..b3a0ef26764 100644 --- a/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart +++ b/packages/flutter_tools/lib/src/build_runner/web_compilation_delegate.dart @@ -2,26 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: implementation_imports import 'dart:async'; -import 'dart:io' as io; // ignore: dart_io_import -import 'package:build/build.dart'; import 'package:build_daemon/client.dart'; +import 'package:build_daemon/constants.dart' as daemon; import 'package:build_daemon/data/build_status.dart'; -import 'package:build_runner_core/build_runner_core.dart' as core; -import 'package:glob/glob.dart'; -import 'package:path/path.dart' as path; +import 'package:build_daemon/data/build_target.dart'; +import 'package:build_daemon/data/server_log.dart'; +import 'package:path/path.dart' as path; // ignore: package_path_import +import '../artifacts.dart'; +import '../base/common.dart'; import '../base/file_system.dart'; import '../build_info.dart'; -import '../convert.dart'; +import '../cache.dart'; import '../globals.dart' as globals; import '../platform_plugins.dart'; import '../plugins.dart'; import '../project.dart'; import '../web/compile.dart'; -import 'web_fs.dart'; /// A build_runner specific implementation of the [WebCompilationProxy]. class BuildRunnerWebCompilationProxy extends WebCompilationProxy { @@ -42,8 +41,8 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { .createSync(); final FlutterProject flutterProject = FlutterProject.fromDirectory(projectDirectory); final bool hasWebPlugins = findPlugins(flutterProject) - .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); - final BuildDaemonClient client = await buildDaemonCreator.startBuildDaemon( + .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); + final BuildDaemonClient client = await const BuildDaemonCreator().startBuildDaemon( projectDirectory.path, release: mode == BuildMode.release, profile: mode == BuildMode.profile, @@ -63,6 +62,9 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { await for (final BuildResults results in client.buildResults) { final BuildResult result = results.results.firstWhere((BuildResult result) { return result.target == 'web'; + }, orElse: () { + // Assume build failed if we lack any results. + return DefaultBuildResult((DefaultBuildResultBuilder b) => b.status == BuildStatus.failed); }); if (result.status == BuildStatus.failed) { success = false; @@ -72,119 +74,188 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy { break; } } - if (success && testOutputDir != null) { - final Directory rootDirectory = projectDirectory - .childDirectory('.dart_tool') - .childDirectory('build') - .childDirectory('flutter_web'); + if (!success || testOutputDir == null) { + return success; + } + final Directory rootDirectory = projectDirectory + .childDirectory('.dart_tool') + .childDirectory('build') + .childDirectory('flutter_web'); - final Iterable childDirectories = rootDirectory - .listSync() - .whereType(); - for (final Directory childDirectory in childDirectories) { - final String path = globals.fs.path.join( - testOutputDir, - 'packages', - globals.fs.path.basename(childDirectory.path), - ); - globals.fsUtils.copyDirectorySync( - childDirectory.childDirectory('lib'), - globals.fs.directory(path), - ); - } - final Directory outputDirectory = rootDirectory - .childDirectory(projectName) - .childDirectory('test'); + final Iterable childDirectories = rootDirectory + .listSync() + .whereType(); + for (final Directory childDirectory in childDirectories) { + final String path = globals.fs.path.join( + testOutputDir, + 'packages', + globals.fs.path.basename(childDirectory.path), + ); globals.fsUtils.copyDirectorySync( - outputDirectory, - globals.fs.directory(globals.fs.path.join(testOutputDir)), + childDirectory.childDirectory('lib'), + globals.fs.directory(path), ); } + final Directory outputDirectory = rootDirectory + .childDirectory(projectName) + .childDirectory('test'); + globals.fsUtils.copyDirectorySync( + outputDirectory, + globals.fs.directory(globals.fs.path.join(testOutputDir)), + ); return success; } } -/// Handles mapping a single root file scheme to a multi-root scheme. -/// -/// This allows one build_runner build to read the output from a previous -/// isolated build. -class MultirootFileBasedAssetReader extends core.FileBasedAssetReader { - MultirootFileBasedAssetReader( - core.PackageGraph packageGraph, - this.generatedDirectory, - ) : super(packageGraph); +class WebTestTargetManifest { + WebTestTargetManifest(this.buildFilters); - final Directory generatedDirectory; + WebTestTargetManifest.all() : buildFilters = null; - @override - Future canRead(AssetId id) { - if (packageGraph[id.package] == packageGraph.root && _missingSource(id)) { - return _generatedFile(id).exists(); + final List buildFilters; + + bool get hasBuildFilters => buildFilters != null && buildFilters.isNotEmpty; +} + +/// A testable interface for starting a build daemon. +class BuildDaemonCreator { + const BuildDaemonCreator(); + + // TODO(jonahwilliams): find a way to get build checks working for flutter for web. + static const String _ignoredLine1 = 'Warning: Interpreting this as package URI'; + static const String _ignoredLine2 = 'build_script.dart was not found in the asset graph, incremental builds will not work'; + static const String _ignoredLine3 = 'have your dependencies specified fully in your pubspec.yaml'; + + /// Start a build daemon and register the web targets. + /// + /// [initializePlatform] controls whether we should invoke [webOnlyInitializePlatform]. + Future startBuildDaemon(String workingDirectory, { + bool release = false, + bool profile = false, + bool hasPlugins = false, + bool initializePlatform = true, + WebTestTargetManifest testTargets, + }) async { + try { + final BuildDaemonClient client = await _connectClient( + workingDirectory, + release: release, + profile: profile, + hasPlugins: hasPlugins, + initializePlatform: initializePlatform, + testTargets: testTargets, + ); + _registerBuildTargets(client, testTargets); + return client; + } on OptionsSkew { + throwToolExit( + 'Incompatible options with current running build daemon.\n\n' + 'Please stop other flutter_tool instances running in this directory ' + 'before starting a new instance with these options.' + ); } - return super.canRead(id); + return null; } - @override - Future> readAsBytes(AssetId id) { - if (packageGraph[id.package] == packageGraph.root && _missingSource(id)) { - return _generatedFile(id).readAsBytes(); - } - return super.readAsBytes(id); - } - - @override - Future readAsString(AssetId id, {Encoding encoding}) { - if (packageGraph[id.package] == packageGraph.root && _missingSource(id)) { - return _generatedFile(id).readAsString(); - } - return super.readAsString(id, encoding: encoding); - } - - @override - Stream findAssets(Glob glob, {String package}) async* { - if (package == null || packageGraph.root.name == package) { - final String generatedRoot = globals.fs.path.join(generatedDirectory.path, packageGraph.root.name); - await for (final io.FileSystemEntity entity in glob.list(followLinks: true, root: packageGraph.root.path)) { - if (entity is io.File && _isNotHidden(entity) && !globals.fs.path.isWithin(generatedRoot, entity.path)) { - yield _fileToAssetId(entity, packageGraph.root); + void _registerBuildTargets( + BuildDaemonClient client, + WebTestTargetManifest testTargets, + ) { + final OutputLocation outputLocation = OutputLocation((OutputLocationBuilder b) => b + ..output = '' + ..useSymlinks = true + ..hoist = false); + client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) => b + ..target = 'web' + ..outputLocation = outputLocation?.toBuilder())); + if (testTargets != null) { + client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) { + b.target = 'test'; + b.outputLocation = outputLocation?.toBuilder(); + if (testTargets.hasBuildFilters) { + b.buildFilters.addAll(testTargets.buildFilters); } - } - if (!globals.fs.isDirectorySync(generatedRoot)) { - return; - } - await for (final io.FileSystemEntity entity in glob.list(followLinks: true, root: generatedRoot)) { - if (entity is io.File && _isNotHidden(entity)) { - yield _fileToAssetId(entity, packageGraph.root, globals.fs.path.relative(generatedRoot), true); - } - } - return; + })); } - yield* super.findAssets(glob, package: package); } - bool _isNotHidden(io.FileSystemEntity entity) { - return !path.basename(entity.path).startsWith('._'); - } + Future _connectClient( + String workingDirectory, { + bool release, + bool profile, + bool hasPlugins, + bool initializePlatform, + WebTestTargetManifest testTargets, + }) { + final String flutterToolsPackages = globals.fs.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + '.packages', + ); + final String buildScript = globals.fs.path.join( + Cache.flutterRoot, + 'packages', + 'flutter_tools', + 'lib', + 'src', + 'build_runner', + 'build_script.dart', + ); + final String flutterWebSdk = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk); - bool _missingSource(AssetId id) { - return !globals.fs.file(path.joinAll([packageGraph.root.path, ...id.pathSegments])).existsSync(); - } + // On Windows we need to call the snapshot directly otherwise + // the process will start in a disjoint cmd without access to + // STDIO. + final List args = [ + globals.artifacts.getArtifactPath(Artifact.engineDartBinary), + '--packages=$flutterToolsPackages', + buildScript, + 'daemon', + '--skip-build-script-check', + '--define', 'flutter_tools:ddc=flutterWebSdk=$flutterWebSdk', + '--define', 'flutter_tools:entrypoint=flutterWebSdk=$flutterWebSdk', + '--define', 'flutter_tools:entrypoint=release=$release', + '--define', 'flutter_tools:entrypoint=profile=$profile', + '--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk', + '--define', 'flutter_tools:shell=hasPlugins=$hasPlugins', + '--define', 'flutter_tools:shell=initializePlatform=$initializePlatform', + // The following will cause build runner to only build tests that were requested. + if (testTargets != null && testTargets.hasBuildFilters) + for (final String buildFilter in testTargets.buildFilters) + '--build-filter=$buildFilter', + ]; - File _generatedFile(AssetId id) { - return globals.fs.file( - path.joinAll([generatedDirectory.path, packageGraph.root.name, ...id.pathSegments]) + return BuildDaemonClient.connect( + workingDirectory, + args, + logHandler: (ServerLog serverLog) { + switch (serverLog.level) { + case Level.SEVERE: + case Level.SHOUT: + // Ignore certain non-actionable messages on startup. + if (serverLog.message.contains(_ignoredLine1) || + serverLog.message.contains(_ignoredLine2) || + serverLog.message.contains(_ignoredLine3)) { + return; + } + globals.printError(serverLog.message); + if (serverLog.error != null) { + globals.printError(serverLog.error); + } + if (serverLog.stackTrace != null) { + globals.printTrace(serverLog.stackTrace); + } + break; + default: + if (serverLog.message.contains('Skipping compiling')) { + globals.printError(serverLog.message); + } else { + globals.printTrace(serverLog.message); + } + } + }, + buildMode: daemon.BuildMode.Manual, ); } - - /// Creates an [AssetId] for [file], which is a part of [packageNode]. - AssetId _fileToAssetId(io.File file, core.PackageNode packageNode, [String root, bool generated = false]) { - final String filePath = path.normalize(file.absolute.path); - String relativePath; - if (generated) { - relativePath = filePath.substring(root.length + 2); - } else { - relativePath = path.relative(filePath, from: packageNode.path); - } - return AssetId(packageNode.name, relativePath); - } } diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart deleted file mode 100644 index 747291a5a73..00000000000 --- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart +++ /dev/null @@ -1,722 +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:async'; -import 'dart:typed_data'; - -import 'package:archive/archive.dart'; -import 'package:build_daemon/client.dart'; -import 'package:build_daemon/constants.dart' as daemon; -import 'package:build_daemon/data/build_status.dart'; -import 'package:build_daemon/data/build_target.dart'; -import 'package:build_daemon/data/server_log.dart'; -import 'package:dwds/asset_handler.dart'; -import 'package:dwds/dwds.dart'; -import 'package:http_multi_server/http_multi_server.dart'; -import 'package:meta/meta.dart'; -import 'package:shelf/shelf.dart'; -import 'package:shelf/shelf_io.dart' as shelf_io; -import 'package:shelf_proxy/shelf_proxy.dart'; -import 'package:mime/mime.dart' as mime; - -import '../artifacts.dart'; -import '../asset.dart'; -import '../base/common.dart'; -import '../base/context.dart'; -import '../base/file_system.dart'; -import '../base/io.dart'; -import '../base/net.dart'; -import '../build_info.dart'; -import '../bundle.dart'; -import '../cache.dart'; -import '../dart/package_map.dart'; -import '../dart/pub.dart'; -import '../globals.dart' as globals; -import '../platform_plugins.dart'; -import '../plugins.dart'; -import '../project.dart'; -import '../web/chrome.dart'; -import '../web/compile.dart'; - -/// The name of the built web project. -const String kBuildTargetName = 'web'; - -/// A factory for creating a [Dwds] instance. -DwdsFactory get dwdsFactory => context.get() ?? Dwds.start; - -/// The [BuildDaemonCreator] instance. -BuildDaemonCreator get buildDaemonCreator => context.get() ?? const BuildDaemonCreator(); - -/// A factory for creating a [WebFs] instance. -WebFsFactory get webFsFactory => context.get() ?? WebFs.start; - -/// A factory for creating an [HttpMultiServer] instance. -HttpMultiServerFactory get httpMultiServerFactory => context.get() ?? HttpMultiServer.bind; - -/// A function with the same signature as [HttpMultiServer.bind]. -typedef HttpMultiServerFactory = Future Function(dynamic address, int port); - -/// A function with the same signature as [Dwds.start]. -typedef DwdsFactory = Future Function({ - @required AssetHandler assetHandler, - @required Stream buildResults, - @required ConnectionProvider chromeConnection, - String hostname, - ReloadConfiguration reloadConfiguration, - bool serveDevTools, - LogWriter logWriter, - bool verbose, - bool enableDebugExtension, - UrlEncoder urlEncoder, -}); - -/// A function with the same signature as [WebFs.start]. -typedef WebFsFactory = Future Function({ - @required String target, - @required FlutterProject flutterProject, - @required BuildInfo buildInfo, - @required bool skipDwds, - @required bool initializePlatform, - @required String hostname, - @required String port, - @required UrlTunneller urlTunneller, - @required List dartDefines, -}); - -/// The dev filesystem responsible for building and serving web applications. -class WebFs { - @visibleForTesting - WebFs( - this._client, - this._server, - this._dwds, - this.uri, - this._assetServer, - this._useBuildRunner, - this._flutterProject, - this._target, - this._buildInfo, - this._initializePlatform, - this._dartDefines, - ); - - /// The server URL. - final String uri; - - final HttpServer _server; - final Dwds _dwds; - final BuildDaemonClient _client; - final AssetServer _assetServer; - final bool _useBuildRunner; - final FlutterProject _flutterProject; - final String _target; - final BuildInfo _buildInfo; - final bool _initializePlatform; - final List _dartDefines; - StreamSubscription _connectedApps; - - static const String _kHostName = 'localhost'; - - Future stop() async { - await _client?.close(); - await _dwds?.stop(); - await _server.close(force: true); - await _connectedApps?.cancel(); - _assetServer?.dispose(); - } - - Future _cachedExtensionFuture; - - /// Connect and retrieve the [DebugConnection] for the current application. - /// - /// Only calls [AppConnection.runMain] on the subsequent connections. - Future connect(bool useDebugExtension) { - final Completer firstConnection = Completer(); - _connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async { - final DebugConnection debugConnection = useDebugExtension - ? await (_cachedExtensionFuture ??= _dwds.extensionDebugConnections.stream.first) - : await _dwds.debugConnection(appConnection); - if (!firstConnection.isCompleted) { - firstConnection.complete(ConnectionResult(appConnection, debugConnection)); - } else { - appConnection.runMain(); - } - }); - return firstConnection.future; - } - - /// Recompile the web application and return whether this was successful. - Future recompile() async { - if (!_useBuildRunner) { - await buildWeb( - _flutterProject, - _target, - _buildInfo, - _initializePlatform, - _dartDefines, - false, - ); - return true; - } - _client.startBuild(); - await for (final BuildResults results in _client.buildResults) { - final BuildResult result = results.results.firstWhere((BuildResult result) { - return result.target == kBuildTargetName; - }); - if (result.status == BuildStatus.failed) { - return false; - } - if (result.status == BuildStatus.succeeded) { - return true; - } - } - return true; - } - - /// Start the web compiler and asset server. - static Future start({ - @required String target, - @required FlutterProject flutterProject, - @required BuildInfo buildInfo, - @required bool skipDwds, - @required bool initializePlatform, - @required String hostname, - @required String port, - @required UrlTunneller urlTunneller, - @required List dartDefines, - }) async { - // workaround for https://github.com/flutter/flutter/issues/38290 - if (!flutterProject.dartTool.existsSync()) { - flutterProject.dartTool.createSync(recursive: true); - } - // Workaround for https://github.com/flutter/flutter/issues/41681. - final String toolPath = globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'); - if (!globals.fs.isFileSync(globals.fs.path.join(toolPath, '.packages'))) { - await pub.get( - context: PubContext.pubGet, - directory: toolPath, - offline: true, - skipPubspecYamlCheck: true, - checkLastModified: false, - ); - } - - final Completer firstBuildCompleter = Completer(); - - // Initialize the asset bundle. - final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); - await assetBundle.build(); - await writeBundle(globals.fs.directory(getAssetBuildDirectory()), assetBundle.entries); - - final String targetBaseName = globals.fs.path - .withoutExtension(target).replaceFirst('lib${globals.fs.path.separator}', ''); - final Map mappedUrls = { - 'main.dart.js': 'packages/${flutterProject.manifest.appName}/' - '${targetBaseName}_web_entrypoint.dart.js', - '${targetBaseName}_web_entrypoint.dart.js.map': 'packages/${flutterProject.manifest.appName}/' - '${targetBaseName}_web_entrypoint.dart.js.map', - '${targetBaseName}_web_entrypoint.dart.bootstrap.js': 'packages/${flutterProject.manifest.appName}/' - '${targetBaseName}_web_entrypoint.dart.bootstrap.js', - '${targetBaseName}_web_entrypoint.digests': 'packages/${flutterProject.manifest.appName}/' - '${targetBaseName}_web_entrypoint.digests', - }; - - // Initialize the dwds server. - final String effectiveHostname = hostname ?? _kHostName; - final int hostPort = port == null ? await globals.os.findFreePort() : int.tryParse(port); - - final Pipeline pipeline = const Pipeline().addMiddleware((Handler innerHandler) { - return (Request request) async { - // Redirect the main.dart.js to the target file we decided to serve. - if (mappedUrls.containsKey(request.url.path)) { - final String newPath = mappedUrls[request.url.path]; - return innerHandler( - Request( - request.method, - Uri.parse(request.requestedUri.toString() - .replaceFirst(request.requestedUri.path, '/$newPath')), - headers: request.headers, - url: Uri.parse(request.url.toString() - .replaceFirst(request.url.path, newPath)), - ), - ); - } else { - return innerHandler(request); - } - }; - }); - - Handler handler; - Dwds dwds; - BuildDaemonClient client; - StreamSubscription firstBuild; - if (buildInfo.isDebug) { - final bool hasWebPlugins = findPlugins(flutterProject) - .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); - // Start the build daemon and run an initial build. - client = await buildDaemonCreator - .startBuildDaemon(globals.fs.currentDirectory.path, - release: buildInfo.isRelease, - profile: buildInfo.isProfile, - hasPlugins: hasWebPlugins, - initializePlatform: initializePlatform, - ); - client.startBuild(); - // Only provide relevant build results - final Stream filteredBuildResults = client.buildResults - .asyncMap((BuildResults results) { - return results.results - .firstWhere((BuildResult result) => result.target == kBuildTargetName); - }); - // Start the build daemon and run an initial build. - firstBuild = client.buildResults.listen((BuildResults buildResults) { - if (firstBuildCompleter.isCompleted) { - return; - } - final BuildResult result = buildResults.results.firstWhere((BuildResult result) { - return result.target == kBuildTargetName; - }); - if (result.status == BuildStatus.failed) { - firstBuildCompleter.complete(false); - } - if (result.status == BuildStatus.succeeded) { - firstBuildCompleter.complete(true); - } - }); - final int daemonAssetPort = buildDaemonCreator.assetServerPort(globals.fs.currentDirectory); - - // Initialize the asset bundle. - final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); - await assetBundle.build(); - await writeBundle(globals.fs.directory(getAssetBuildDirectory()), assetBundle.entries); - if (!skipDwds) { - final BuildRunnerAssetHandler assetHandler = BuildRunnerAssetHandler( - daemonAssetPort, - kBuildTargetName, - effectiveHostname, - hostPort); - dwds = await dwdsFactory( - hostname: effectiveHostname, - assetHandler: assetHandler, - buildResults: filteredBuildResults, - chromeConnection: () async { - return (await ChromeLauncher.connectedInstance).chromeConnection; - }, - reloadConfiguration: ReloadConfiguration.none, - serveDevTools: false, - verbose: false, - enableDebugExtension: true, - urlEncoder: urlTunneller, - logWriter: (dynamic level, String message) => globals.printTrace(message), - ); - handler = pipeline.addHandler(dwds.handler); - } else { - handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/')); - } - } else { - await buildWeb( - flutterProject, - target, - buildInfo, - initializePlatform, - dartDefines, - false, - ); - firstBuildCompleter.complete(true); - } - - final AssetServer assetServer = buildInfo.isDebug - ? DebugAssetServer(flutterProject, targetBaseName) - : ReleaseAssetServer(); - Cascade cascade = Cascade(); - cascade = cascade.add(handler); - cascade = cascade.add(assetServer.handle); - final InternetAddress internetAddress = (await InternetAddress.lookup(effectiveHostname)).first; - final HttpServer server = await httpMultiServerFactory(internetAddress, hostPort); - shelf_io.serveRequests(server, cascade.handler); - final WebFs webFS = WebFs( - client, - server, - dwds, - 'http://$effectiveHostname:$hostPort', - assetServer, - buildInfo.isDebug, - flutterProject, - target, - buildInfo, - initializePlatform, - dartDefines, - ); - if (!await firstBuildCompleter.future) { - throw const BuildException(); - } - await firstBuild?.cancel(); - return webFS; - } -} - -/// An exception thrown when build runner fails. -/// -/// This contains no error information as it will have already been printed to -/// the console. -class BuildException implements Exception { - const BuildException(); -} - -abstract class AssetServer { - Future handle(Request request); - - void dispose() {} -} - -class ReleaseAssetServer extends AssetServer { - // Locations where source files, assets, or source maps may be located. - final List _searchPaths = [ - globals.fs.directory(getWebBuildDirectory()).uri, - globals.fs.directory(Cache.flutterRoot).parent.uri, - globals.fs.currentDirectory.childDirectory('lib').uri, - ]; - - @override - Future handle(Request request) async { - Uri fileUri; - for (final Uri uri in _searchPaths) { - final Uri potential = uri.resolve(request.url.path); - if (potential == null || !globals.fs.isFileSync(potential.toFilePath())) { - continue; - } - fileUri = potential; - break; - } - - if (fileUri != null) { - final File file = globals.fs.file(fileUri); - final Uint8List bytes = file.readAsBytesSync(); - // Fallback to "application/octet-stream" on null which - // makes no claims as to the structure of the data. - final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) - ?? 'application/octet-stream'; - return Response.ok(bytes, headers: { - 'Content-Type': mimeType, - }); - } - if (request.url.path == '') { - final File file = globals.fs.file(globals.fs.path.join(getWebBuildDirectory(), 'index.html')); - return Response.ok(file.readAsBytesSync(), headers: { - 'Content-Type': 'text/html', - }); - } - return Response.notFound(''); - } -} - -class DebugAssetServer extends AssetServer { - DebugAssetServer(this.flutterProject, this.targetBaseName); - - final FlutterProject flutterProject; - final String targetBaseName; - final PackageMap packageMap = PackageMap(PackageMap.globalPackagesPath); - Directory partFiles; - - @override - Future handle(Request request) async { - if (request.url.path.endsWith('.html')) { - final Uri htmlUri = flutterProject.web.directory.uri.resolveUri(request.url); - final File htmlFile = globals.fs.file(htmlUri); - if (htmlFile.existsSync()) { - return Response.ok(htmlFile.readAsBytesSync(), headers: { - 'Content-Type': 'text/html', - }); - } - return Response.notFound(''); - } else if (request.url.path.contains('stack_trace_mapper')) { - final File file = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath), - 'lib', - 'dev_compiler', - 'web', - 'dart_stack_trace_mapper.js', - )); - return Response.ok(file.readAsBytesSync(), headers: { - 'Content-Type': 'text/javascript', - }); - } else if (request.url.path.endsWith('part.js')) { - // Lazily unpack any deferred imports in release/profile mode. These are - // placed into an archive by build_runner, and are named based on the main - // entrypoint + a "part" suffix (Though the actual names are arbitrary). - // To make this easier to deal with they are copied into a temp directory. - if (partFiles == null) { - final File dart2jsArchive = globals.fs.file(globals.fs.path.join( - flutterProject.dartTool.path, - 'build', - 'flutter_web', - flutterProject.manifest.appName, - 'lib', - '${targetBaseName}_web_entrypoint.dart.js.tar.gz', - )); - if (dart2jsArchive.existsSync()) { - final Archive archive = TarDecoder().decodeBytes(dart2jsArchive.readAsBytesSync()); - partFiles = globals.fs.systemTempDirectory.createTempSync('flutter_tool.') - ..createSync(); - for (final ArchiveFile file in archive) { - partFiles.childFile(file.name).writeAsBytesSync(file.content as List); - } - } - } - final String fileName = globals.fs.path.basename(request.url.path); - return Response.ok(partFiles.childFile(fileName).readAsBytesSync(), headers: { - 'Content-Type': 'text/javascript', - }); - } else if (request.url.path.contains('require.js')) { - final File file = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath), - 'lib', - 'dev_compiler', - 'kernel', - 'amd', - 'require.js', - )); - return Response.ok(file.readAsBytesSync(), headers: { - 'Content-Type': 'text/javascript', - }); - } else if (request.url.path.endsWith('dart_sdk.js.map')) { - final File file = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd', - 'dart_sdk.js.map', - )); - return Response.ok(file.readAsBytesSync()); - } else if (request.url.path.endsWith('dart_sdk.js')) { - final File file = globals.fs.file(globals.fs.path.join( - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), - 'kernel', - 'amd', - 'dart_sdk.js', - )); - return Response.ok(file.readAsBytesSync(), headers: { - 'Content-Type': 'text/javascript', - }); - } else if (request.url.path.endsWith('.dart')) { - // This is likely a sourcemap request. The first segment is the - // package name, and the rest is the path to the file relative to - // the package uri. For example, `foo/bar.dart` would represent a - // file at a path like `foo/lib/bar.dart`. If there is no leading - // segment, then we assume it is from the current package. - String basePath = request.url.path; - basePath = basePath.replaceFirst('packages/build_web_compilers/', ''); - basePath = basePath.replaceFirst('packages/', ''); - - // Handle sdk requests that have mangled urls from engine build. - if (request.url.path.contains('dart-sdk')) { - // Note: the request is a uri and not a file path, so they always use `/`. - final String sdkPath = globals.fs.path.joinAll(request.url.path.split('dart-sdk/').last.split('/')); - final String dartSdkPath = globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath); - final File candidateFile = globals.fs.file(globals.fs.path.join(dartSdkPath, sdkPath)); - return Response.ok(candidateFile.readAsBytesSync()); - } - - // See if it is a flutter sdk path. - final String webSdkPath = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk); - final File candidateFile = globals.fs.file(globals.fs.path.join(webSdkPath, - basePath.split('/').join(globals.platform.pathSeparator))); - if (candidateFile.existsSync()) { - return Response.ok(candidateFile.readAsBytesSync()); - } - - final String packageName = request.url.pathSegments.length == 1 - ? flutterProject.manifest.appName - : request.url.pathSegments.first; - String filePath = globals.fs.path.joinAll(request.url.pathSegments.length == 1 - ? request.url.pathSegments - : request.url.pathSegments.skip(1)); - String packagePath = packageMap.map[packageName]?.toFilePath(windows: globals.platform.isWindows); - // If the package isn't found, then we have an issue with relative - // paths within the main project. - if (packagePath == null) { - packagePath = packageMap.map[flutterProject.manifest.appName] - .toFilePath(windows: globals.platform.isWindows); - filePath = request.url.path; - } - final File file = globals.fs.file(globals.fs.path.join(packagePath, filePath)); - if (file.existsSync()) { - return Response.ok(file.readAsBytesSync()); - } - return Response.notFound(''); - } else if (request.url.path.contains('assets')) { - final String assetPath = request.url.path.replaceFirst('assets/', ''); - final File file = globals.fs.file(globals.fs.path.join(getAssetBuildDirectory(), assetPath)); - if (file.existsSync()) { - final Uint8List bytes = file.readAsBytesSync(); - // Fallback to "application/octet-stream" on null which - // makes no claims as to the structure of the data. - final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) - ?? 'application/octet-stream'; - return Response.ok(bytes, headers: { - 'Content-Type': mimeType, - }); - } else { - return Response.notFound(''); - } - } - return Response.notFound(''); - } - - @override - void dispose() { - partFiles?.deleteSync(recursive: true); - } -} - -class ConnectionResult { - ConnectionResult(this.appConnection, this.debugConnection); - - final AppConnection appConnection; - final DebugConnection debugConnection; -} - -class WebTestTargetManifest { - WebTestTargetManifest(this.buildFilters); - - WebTestTargetManifest.all() : buildFilters = null; - - final List buildFilters; - - bool get hasBuildFilters => buildFilters != null && buildFilters.isNotEmpty; -} - -/// A testable interface for starting a build daemon. -class BuildDaemonCreator { - const BuildDaemonCreator(); - - // TODO(jonahwilliams): find a way to get build checks working for flutter for web. - static const String _ignoredLine1 = 'Warning: Interpreting this as package URI'; - static const String _ignoredLine2 = 'build_script.dart was not found in the asset graph, incremental builds will not work'; - static const String _ignoredLine3 = 'have your dependencies specified fully in your pubspec.yaml'; - - /// Start a build daemon and register the web targets. - /// - /// [initializePlatform] controls whether we should invoke [webOnlyInitializePlatform]. - Future startBuildDaemon(String workingDirectory, { - bool release = false, - bool profile = false, - bool hasPlugins = false, - bool initializePlatform = true, - WebTestTargetManifest testTargets, - }) async { - try { - final BuildDaemonClient client = await _connectClient( - workingDirectory, - release: release, - profile: profile, - hasPlugins: hasPlugins, - initializePlatform: initializePlatform, - testTargets: testTargets, - ); - _registerBuildTargets(client, testTargets); - return client; - } on OptionsSkew { - throwToolExit( - 'Incompatible options with current running build daemon.\n\n' - 'Please stop other flutter_tool instances running in this directory ' - 'before starting a new instance with these options.'); - } - return null; - } - - void _registerBuildTargets( - BuildDaemonClient client, - WebTestTargetManifest testTargets, - ) { - final OutputLocation outputLocation = OutputLocation((OutputLocationBuilder b) => b - ..output = '' - ..useSymlinks = true - ..hoist = false); - client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) => b - ..target = 'web' - ..outputLocation = outputLocation?.toBuilder())); - if (testTargets != null) { - client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) { - b.target = 'test'; - b.outputLocation = outputLocation?.toBuilder(); - if (testTargets.hasBuildFilters) { - b.buildFilters.addAll(testTargets.buildFilters); - } - })); - } - } - - Future _connectClient( - String workingDirectory, { - bool release, - bool profile, - bool hasPlugins, - bool initializePlatform, - WebTestTargetManifest testTargets, - }) { - final String flutterToolsPackages = globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools', '.packages'); - final String buildScript = globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools', 'lib', 'src', 'build_runner', 'build_script.dart'); - final String flutterWebSdk = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk); - - // On Windows we need to call the snapshot directly otherwise - // the process will start in a disjoint cmd without access to - // STDIO. - final List args = [ - globals.artifacts.getArtifactPath(Artifact.engineDartBinary), - '--packages=$flutterToolsPackages', - buildScript, - 'daemon', - '--skip-build-script-check', - '--define', 'flutter_tools:ddc=flutterWebSdk=$flutterWebSdk', - '--define', 'flutter_tools:entrypoint=flutterWebSdk=$flutterWebSdk', - '--define', 'flutter_tools:entrypoint=release=$release', - '--define', 'flutter_tools:entrypoint=profile=$profile', - '--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk', - '--define', 'flutter_tools:shell=hasPlugins=$hasPlugins', - '--define', 'flutter_tools:shell=initializePlatform=$initializePlatform', - // The following will cause build runner to only build tests that were requested. - if (testTargets != null && testTargets.hasBuildFilters) - for (final String buildFilter in testTargets.buildFilters) - '--build-filter=$buildFilter', - ]; - - return BuildDaemonClient.connect( - workingDirectory, - args, - logHandler: (ServerLog serverLog) { - switch (serverLog.level) { - case Level.SEVERE: - case Level.SHOUT: - // Ignore certain non-actionable messages on startup. - if (serverLog.message.contains(_ignoredLine1) || - serverLog.message.contains(_ignoredLine2) || - serverLog.message.contains(_ignoredLine3)) { - return; - } - globals.printError(serverLog.message); - if (serverLog.error != null) { - globals.printError(serverLog.error); - } - if (serverLog.stackTrace != null) { - globals.printTrace(serverLog.stackTrace); - } - break; - default: - if (serverLog.message.contains('Skipping compiling')) { - globals.printError(serverLog.message); - } else { - globals.printTrace(serverLog.message); - } - } - }, - buildMode: daemon.BuildMode.Manual, - ); - } - - /// Retrieve the asset server port for the current daemon. - int assetServerPort(Directory workingDirectory) { - final String portFilePath = globals.fs.path.join(daemon.daemonWorkspace(workingDirectory.path), '.asset_server_port'); - return int.tryParse(globals.fs.file(portFilePath).readAsStringSync()); - } -} diff --git a/packages/flutter_tools/lib/src/codegen.dart b/packages/flutter_tools/lib/src/codegen.dart index 44cdafc8f63..e4332345122 100644 --- a/packages/flutter_tools/lib/src/codegen.dart +++ b/packages/flutter_tools/lib/src/codegen.dart @@ -192,6 +192,9 @@ class CodeGeneratingResidentCompiler implements ResidentCompiler { Future shutdown() { return _residentCompiler.shutdown(); } + + @override + void addFileSystemRoot(String root) { } } /// The current status of a codegen build. diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index acece5e1525..8f6c03ec312 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -25,7 +25,7 @@ const Map _kManuallyPinnedDependencies = { 'flutter_gallery_assets': '0.1.9+2', // See //examples/flutter_gallery/pubspec.yaml 'mockito': '^4.1.0', // Prevent mockito from downgrading to 4.0.0 'vm_service_client': '0.2.6+2', // Final version before being marked deprecated. - 'dwds': '0.8.5', // Requires updates to web_fs due to breaking changes. + 'video_player': '0.10.6', // 0.10.7 fails a gallery smoke test for toString. }; class UpdatePackagesCommand extends FlutterCommand { diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index c00182118ad..606d5b2d949 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -197,7 +197,8 @@ class StdoutHandler { class PackageUriMapper { PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List fileSystemRoots) { final Map packageMap = PackageMap(globals.fs.path.absolute(packagesPath)).map; - final String scriptUri = Uri.file(scriptPath, windows: globals.platform.isWindows).toString(); + final bool isWindowsPath = globals.platform.isWindows && !scriptPath.startsWith('org-dartlang-app'); + final String scriptUri = Uri.file(scriptPath, windows: isWindowsPath).toString(); for (final String packageName in packageMap.keys) { final String prefix = packageMap[packageName].toString(); // Only perform a multi-root mapping if there are multiple roots. @@ -463,6 +464,11 @@ abstract class ResidentCompiler { List dartDefines, }) = DefaultResidentCompiler; + // TODO(jonahwilliams): find a better way to configure additional file system + // roots from the runner. + // See: https://github.com/flutter/flutter/issues/50494 + void addFileSystemRoot(String root); + /// If invoked for the first time, it compiles Dart script identified by /// [mainPath], [invalidatedFiles] list is ignored. @@ -538,6 +544,11 @@ class DefaultResidentCompiler implements ResidentCompiler { final List experimentalFlags; final List dartDefines; + @override + void addFileSystemRoot(String root) { + fileSystemRoots.add(root); + } + /// The path to the root of the Dart SDK used to compile. /// /// This is used to resolve the [platformDill]. diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index ab9e3b0a95f..a1f8dc14d04 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -37,9 +37,6 @@ class FeatureFlags { /// Whether the Android embedding V2 is enabled. bool get isAndroidEmbeddingV2Enabled => isEnabled(flutterAndroidEmbeddingV2Feature); - /// Whether the web incremental compiler is enabled. - bool get isWebIncrementalCompilerEnabled => isEnabled(flutterWebIncrementalCompiler); - /// Whether a particular feature is enabled for the current channel. /// /// Prefer using one of the specific getters above instead of this API. @@ -72,7 +69,6 @@ const List allFeatures = [ flutterMacOSDesktopFeature, flutterWindowsDesktopFeature, flutterAndroidEmbeddingV2Feature, - flutterWebIncrementalCompiler, ]; /// The [Feature] for flutter web. @@ -154,21 +150,6 @@ const Feature flutterAndroidEmbeddingV2Feature = Feature( ), ); -/// The [Feature] for using the incremental compiler instead of build runner. -const Feature flutterWebIncrementalCompiler = Feature( - name: 'Enable the incremental compiler for web builds', - configSetting: 'enable-web-incremental-compiler', - environmentOverride: 'WEB_INCREMENTAL_COMPILER', - master: FeatureChannelSetting( - available: true, - enabledByDefault: false, - ), - dev: FeatureChannelSetting( - available: true, - enabledByDefault: false, - ), -); - /// A [Feature] is a process for conditionally enabling tool features. /// /// All settings are optional, and if not provided will generally default to diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 2bdf436135d..122788f70ab 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -52,7 +52,7 @@ class FlutterDevice { ), buildMode: buildMode, trackWidgetCreation: trackWidgetCreation, - fileSystemRoots: fileSystemRoots, + fileSystemRoots: fileSystemRoots ?? [], fileSystemScheme: fileSystemScheme, targetModel: targetModel, experimentalFlags: experimentalFlags, @@ -79,14 +79,15 @@ class FlutterDevice { if (device.platformType == PlatformType.fuchsia) { targetModel = TargetModel.flutterRunner; } - if (featureFlags.isWebIncrementalCompilerEnabled && - targetPlatform == TargetPlatform.web_javascript) { + if (targetPlatform == TargetPlatform.web_javascript) { generator = ResidentCompiler( globals.artifacts.getArtifactPath(Artifact.flutterWebSdk, mode: buildMode), buildMode: buildMode, trackWidgetCreation: trackWidgetCreation, - fileSystemRoots: fileSystemRoots, - fileSystemScheme: fileSystemScheme, + fileSystemRoots: fileSystemRoots ?? [], + // Override the filesystem scheme so that the frontend_server can find + // the generated entrypoint code. + fileSystemScheme: 'org-dartlang-app', targetModel: TargetModel.dartdevc, experimentalFlags: experimentalFlags, platformDill: globals.fs.file(globals.artifacts diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index 4f17576a799..e89dc4227c8 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart @@ -54,7 +54,7 @@ class CoverageCollector extends TestWatcher { assert(data != null); print('($observatoryUri): collected coverage data; merging...'); - _addHitmap(coverage.createHitmap(data['coverage'] as List)); + _addHitmap(coverage.createHitmap(data['coverage'] as List>)); print('($observatoryUri): done merging coverage data into global coverage map.'); } @@ -86,7 +86,7 @@ class CoverageCollector extends TestWatcher { assert(data != null); globals.printTrace('pid $pid ($observatoryUri): collected coverage data; merging...'); - _addHitmap(coverage.createHitmap(data['coverage'] as List)); + _addHitmap(coverage.createHitmap(data['coverage'] as List>)); globals.printTrace('pid $pid ($observatoryUri): done merging coverage data into global coverage map.'); } diff --git a/packages/flutter_tools/lib/src/web/bootstrap.dart b/packages/flutter_tools/lib/src/web/bootstrap.dart index 92f2b2cfee2..00bd5e2ff62 100644 --- a/packages/flutter_tools/lib/src/web/bootstrap.dart +++ b/packages/flutter_tools/lib/src/web/bootstrap.dart @@ -4,6 +4,39 @@ import 'package:meta/meta.dart'; +// This logic is taken directly from https://github.com/dart-lang/build/blob/master/build_web_compilers/lib/src/dev_compiler_bootstrap.dart#L272 +// It should be fairly stable, but is otherwise required to interact with the client.js script +// vendored with DWDS. +const String _currentDirectoryScript = r''' +var _currentDirectory = (function () { + var _url; + var lines = new Error().stack.split('\n'); + function lookupUrl() { + if (lines.length > 2) { + var match = lines[1].match(/^\s+at (.+):\d+:\d+$/); + // Chrome. + if (match) return match[1]; + // Chrome nested eval case. + match = lines[1].match(/^\s+at eval [(](.+):\d+:\d+[)]$/); + if (match) return match[1]; + // Edge. + match = lines[1].match(/^\s+at.+\((.+):\d+:\d+\)$/); + if (match) return match[1]; + // Firefox. + match = lines[0].match(/[<][@](.+):\d+:\d+$/) + if (match) return match[1]; + } + // Safari. + return lines[0].match(/(.+):\d+:\d+$/)[1]; + } + _url = lookupUrl(); + var lastSlash = _url.lastIndexOf('/'); + if (lastSlash == -1) return _url; + var currentDirectory = _url.substring(0, lastSlash + 1); + return currentDirectory; +})(); +'''; + /// The JavaScript bootstrap script to support in-browser hot restart. /// /// The [requireUrl] loads our cached RequireJS script file. The [mapperUrl] @@ -34,7 +67,7 @@ requireEl.defer = true; requireEl.async = false; requireEl.src = "$requireUrl"; // This attribute tells require JS what to load as main (defined below). -requireEl.setAttribute("data-main", "main_module"); +requireEl.setAttribute("data-main", "main_module.bootstrap"); document.head.appendChild(requireEl); // Invoked by connected chrome debugger for hot reload/restart support. @@ -55,8 +88,8 @@ window.\$hotReloadHook = function(modules) { // once we've reloaded every module, trigger the hot reload. if (reloadCount == modules.length) { require(["$entrypoint", "dart_sdk"], function(app, dart_sdk) { - // See L81 below for an explanation. - window.\$mainEntrypoint = app[Object.keys(app)[0]].main; + // See the doc comment under in generateMainModule. + window.\$dartRunMain = app[Object.keys(app)[0]].main; window.\$hotReload(resolve); }); } @@ -69,33 +102,108 @@ window.\$hotReloadHook = function(modules) { /// Generate a synthetic main module which captures the application's main /// method. +/// +/// RE: Object.keys usage in app.main: +/// This attaches the main entrypoint and hot reload functionality to the window. +/// The app module will have a single property which contains the actual application +/// code. The property name is based off of the entrypoint that is generated, for example +/// the file `foo/bar/baz.dart` will generate a property named approximately +/// `foo__bar__baz`. Rather than attempt to guess, we assume the first property of +/// this object is the module. String generateMainModule({@required String entrypoint}) { - return ''' + return '''/* ENTRYPOINT_EXTENTION_MARKER */ +// baseUrlScript +var baseUrl = (function () { + // Attempt to detect --precompiled mode for tests, and set the base url + // appropriately, otherwise set it to '/'. + var pathParts = location.pathname.split("/"); + if (pathParts[0] == "") { + pathParts.shift(); + } + if (pathParts.length > 1 && pathParts[1] == "test") { + return "/" + pathParts.slice(0, 2).join("/") + "/"; + } + // Attempt to detect base url using html tag + // base href should start and end with "/" + if (typeof document !== 'undefined') { + var el = document.getElementsByTagName('base'); + if (el && el[0] && el[0].getAttribute("href") && el[0].getAttribute + ("href").startsWith("/") && el[0].getAttribute("href").endsWith("/")){ + return el[0].getAttribute("href"); + } + } + // return default value + return "/"; +}()); +$_currentDirectoryScript +// dart loader +if(!window.\$dartLoader) { + window.\$dartLoader = { + appDigests: _currentDirectory + 'basic.digests', + moduleIdToUrl: new Map(), + urlToModuleId: new Map(), + rootDirectories: new Array(), + // Used in package:build_runner/src/server/build_updates_client/hot_reload_client.dart + moduleParentsGraph: new Map(), + moduleLoadingErrorCallbacks: new Map(), + forceLoadModule: function (moduleName, callback, onError) { + if (typeof onError != 'undefined') { + var errorCallbacks = \$dartLoader.moduleLoadingErrorCallbacks; + if (!errorCallbacks.has(moduleName)) { + errorCallbacks.set(moduleName, new Set()); + } + errorCallbacks.get(moduleName).add(onError); + } + requirejs.undef(moduleName); + requirejs([moduleName], function() { + if (typeof onError != 'undefined') { + errorCallbacks.get(moduleName).delete(onError); + } + if (typeof callback != 'undefined') { + callback(); + } + }); + }, + getModuleLibraries: null, // set up by _initializeTools + }; +} +let modulePaths = {}; +let customModulePaths = {}; +window.\$dartLoader.rootDirectories.push(window.location.origin + baseUrl); +for (let moduleName of Object.getOwnPropertyNames(modulePaths)) { + let modulePath = modulePaths[moduleName]; + if (modulePath != moduleName) { + customModulePaths[moduleName] = modulePath; + } + var src = window.location.origin + '/' + modulePath + '.js'; + if (window.\$dartLoader.moduleIdToUrl.has(moduleName)) { + continue; + } + \$dartLoader.moduleIdToUrl.set(moduleName, src); + \$dartLoader.urlToModuleId.set(src, moduleName); +} // Create the main module loaded below. -define("main_module", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { +define("main_module.bootstrap", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) { dart_sdk.dart.setStartAsyncSynchronously(true); dart_sdk._isolate_helper.startRootIsolate(() => {}, []); dart_sdk._debugger.registerDevtoolsFormatter(); let voidToNull = () => (voidToNull = dart_sdk.dart.constFn(dart_sdk.dart.fnType(dart_sdk.core.Null, [dart_sdk.dart.void])))(); - // Attach the main entrypoint and hot reload functionality to the window. - // The app module will have a single property which contains the actual application - // code. The property name is based off of the entrypoint that is generated, for example - // the file `foo/bar/baz.dart` will generate a property named approximately - // `foo__bar__baz`. Rather than attempt to guess, we assume the first property of - // this object is the module. - window.\$mainEntrypoint = app[Object.keys(app)[0]].main; + // See the generateMainModule doc comment. + var child = {}; + child.main = app[Object.keys(app)[0]].main; if (window.\$hotReload == null) { window.\$hotReload = function(cb) { dart_sdk.developer.invokeExtension("ext.flutter.disassemble", "{}").then((_) => { dart_sdk.dart.hotRestart(); - window.\$mainEntrypoint(); + window.\$dartRunMain(); window.requestAnimationFrame(cb); }); } } - window.\$mainEntrypoint(); + /* MAIN_EXTENSION_MARKER */ + child.main(); }); // Require JS configuration. diff --git a/packages/flutter_tools/lib/src/web/devfs_web.dart b/packages/flutter_tools/lib/src/web/devfs_web.dart index fcac89cfb4b..127038fa2c5 100644 --- a/packages/flutter_tools/lib/src/web/devfs_web.dart +++ b/packages/flutter_tools/lib/src/web/devfs_web.dart @@ -2,39 +2,42 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:typed_data'; +import 'package:dwds/data/build_result.dart'; +import 'package:dwds/dwds.dart'; +import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:mime/mime.dart' as mime; import 'package:package_config/discovery.dart'; import 'package:package_config/packages.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf/shelf_io.dart' as shelf; import '../artifacts.dart'; import '../asset.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; +import '../base/net.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../bundle.dart'; +import '../cache.dart'; import '../compile.dart'; import '../convert.dart'; import '../devfs.dart'; import '../globals.dart' as globals; import 'bootstrap.dart'; +import 'chrome.dart'; /// A web server which handles serving JavaScript and assets. /// /// This is only used in development mode. -class WebAssetServer { +class WebAssetServer implements AssetReader { @visibleForTesting - WebAssetServer(this._httpServer, this._packages, this.internetAddress, - {@required void Function(dynamic, StackTrace) onError}) { - _httpServer.listen((HttpRequest request) { - _handleRequest(request).catchError(onError); - // TODO(jonahwilliams): test the onError callback when https://github.com/dart-lang/sdk/issues/39094 is fixed. - }, onError: onError); - } + WebAssetServer(this._httpServer, this._packages, this.internetAddress); // Fallback to "application/octet-stream" on null which // makes no claims as to the structure of the data. @@ -44,18 +47,49 @@ class WebAssetServer { /// /// Unhandled exceptions will throw a [ToolExit] with the error and stack /// trace. - static Future start(String hostname, int port) async { + static Future start( + String hostname, + int port, + UrlTunneller urlTunneller, + BuildMode buildMode, + bool enableDwds, + ) async { try { final InternetAddress address = (await InternetAddress.lookup(hostname)).first; final HttpServer httpServer = await HttpServer.bind(address, port); - final Packages packages = - await loadPackagesFile(Uri.base.resolve('.packages'), loader: (Uri uri) => globals.fs.file(uri).readAsBytes()); - return WebAssetServer(httpServer, packages, address, - onError: (dynamic error, StackTrace stackTrace) { - httpServer.close(force: true); - throwToolExit( - 'Unhandled exception in web development server:\n$error\n$stackTrace'); - }); + final Packages packages = await loadPackagesFile( + Uri.base.resolve('.packages'), loader: (Uri uri) => globals.fs.file(uri).readAsBytes()); + final WebAssetServer server = WebAssetServer(httpServer, packages, address); + + // In release builds deploy a simpler proxy server. + if (buildMode != BuildMode.debug) { + final ReleaseAssetServer releaseAssetServer = ReleaseAssetServer(); + shelf.serveRequests(httpServer, releaseAssetServer.handle); + return server; + } + // In debug builds, spin up DWDS and the full asset server. + final Dwds dwds = await Dwds.start( + assetReader: server, + buildResults: const Stream.empty(), + chromeConnection: () async { + final Chrome chrome = await ChromeLauncher.connectedInstance; + return chrome.chromeConnection; + }, + urlEncoder: urlTunneller, + enableDebugging: true, + logWriter: (Level logLevel, String message) => globals.printTrace(message) + ); + shelf.Pipeline pipeline = const shelf.Pipeline(); + if (enableDwds) { + pipeline = pipeline.addMiddleware(dwds.middleware); + } + final shelf.Handler dwdsHandler = pipeline.addHandler(server.handleRequest); + final shelf.Cascade cascade = shelf.Cascade() + .add(dwds.handler) + .add(dwdsHandler); + shelf.serveRequests(httpServer, cascade.handler); + server.dwds = dwds; + return server; } on SocketException catch (err) { throwToolExit('Failed to bind web development server:\n$err'); } @@ -68,10 +102,9 @@ class WebAssetServer { // RandomAccessFile and read on demand. final Map _files = {}; final Map _sourcemaps = {}; - final RegExp _drivePath = RegExp(r'\/[A-Z]:\/'); - final Packages _packages; final InternetAddress internetAddress; + /* late final */ Dwds dwds; @visibleForTesting Uint8List getFile(String path) => _files[path]; @@ -80,97 +113,58 @@ class WebAssetServer { Uint8List getSourceMap(String path) => _sourcemaps[path]; // handle requests for JavaScript source, dart sources maps, or asset files. - Future _handleRequest(HttpRequest request) async { - final HttpResponse response = request.response; + @visibleForTesting + Future handleRequest(shelf.Request request) async { + final Map headers = {}; // If the response is `/`, then we are requesting the index file. - if (request.uri.path == '/') { + if (request.url.path == '/' || request.url.path.isEmpty) { final File indexFile = globals.fs.currentDirectory - .childDirectory('web') - .childFile('index.html'); + .childDirectory('web') + .childFile('index.html'); if (indexFile.existsSync()) { - response.headers.add('Content-Type', 'text/html'); - response.headers.add('Content-Length', indexFile.lengthSync()); - await response.addStream(indexFile.openRead()); - } else { - response.statusCode = HttpStatus.notFound; + headers[HttpHeaders.contentTypeHeader] = 'text/html'; + headers[HttpHeaders.contentLengthHeader] = indexFile.lengthSync().toString(); + return shelf.Response.ok(indexFile.openRead(), headers: headers); } - await response.close(); - return; + return shelf.Response.notFound(''); } - // TODO(jonahwilliams): better path normalization in frontend_server to remove - // this workaround. - String requestPath = request.uri.path; - if (requestPath.startsWith(_drivePath)) { - requestPath = requestPath.substring(3); - } + // NOTE: shelf removes leading `/` for some reason. + final String requestPath = request.url.path.startsWith('/') + ? request.url.path + : '/${request.url.path}'; // If this is a JavaScript file, it must be in the in-memory cache. // Attempt to look up the file by URI. if (_files.containsKey(requestPath)) { final List bytes = getFile(requestPath); - response.headers - ..add('Content-Length', bytes.length) - ..add('Content-Type', 'application/javascript'); - response.add(bytes); - await response.close(); - return; + headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); + headers[HttpHeaders.contentTypeHeader] = 'application/javascript'; + return shelf.Response.ok(bytes, headers: headers); } // If this is a sourcemap file, then it might be in the in-memory cache. // Attempt to lookup the file by URI. if (_sourcemaps.containsKey(requestPath)) { final List bytes = getSourceMap(requestPath); - response.headers - ..add('Content-Length', bytes.length) - ..add('Content-Type', 'application/json'); - response.add(bytes); - await response.close(); - return; + headers[HttpHeaders.contentLengthHeader] = bytes.length.toString(); + headers[HttpHeaders.contentTypeHeader] = 'application/json'; + return shelf.Response.ok(bytes, headers: headers); } - // If this is a dart file, it must be on the local file system and is - // likely coming from a source map request. Attempt to look in the - // local filesystem for it, and return a 404 if it is not found. The tool - // doesn't currently consider the case of Dart files as assets. - File file = globals.fs.file(Uri.base.resolve(request.uri.path)); - - // If both of the lookups above failed, the file might have been a package - // file which is signaled by a `/packages//` request. - if (!file.existsSync() && request.uri.pathSegments.first == 'packages') { - file = globals.fs.file(_packages.resolve(Uri( - scheme: 'package', pathSegments: request.uri.pathSegments.skip(1)))); - } + File file = _resolveDartFile(requestPath); // If all of the lookups above failed, the file might have been an asset. // Try and resolve the path relative to the built asset directory. if (!file.existsSync()) { - final String assetPath = request.uri.path.replaceFirst('/assets/', ''); - file = globals.fs.file(globals.fs.path - .join(getAssetBuildDirectory(), globals.fs.path.relative(assetPath))); - } - - // If it isn't a project source or an asset, it must be a dart SDK source. - // or a flutter web SDK source. - if (!file.existsSync()) { - final Directory dartSdkParent = globals.fs - .directory( - globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath)) - .parent; - file = globals.fs.file(globals.fs.path - .joinAll([dartSdkParent.path, ...request.uri.pathSegments])); + final String assetPath = requestPath.replaceFirst('/assets/', ''); + file = globals.fs.file( + globals.fs.path.join(getAssetBuildDirectory(), + globals.fs.path.relative(assetPath)), + ); } if (!file.existsSync()) { - final String flutterWebSdk = - globals.artifacts.getArtifactPath(Artifact.flutterWebSdk); - file = globals.fs.file(globals.fs.path - .joinAll([flutterWebSdk, ...request.uri.pathSegments])); - } - - if (!file.existsSync()) { - response.statusCode = HttpStatus.notFound; - await response.close(); - return; + return shelf.Response.notFound(''); } final int length = file.lengthSync(); // Attempt to determine the file's mime type. if this is not provided some @@ -184,10 +178,9 @@ class WebAssetServer { ); } mimeType ??= _kDefaultMimeType; - response.headers.add('Content-Length', length); - response.headers.add('Content-Type', mimeType); - await response.addStream(file.openRead()); - await response.close(); + headers[HttpHeaders.contentLengthHeader] = length.toString(); + headers[HttpHeaders.contentTypeHeader] = mimeType; + return shelf.Response.ok(file.openRead(), headers: headers); } /// Tear down the http server running. @@ -207,19 +200,15 @@ class WebAssetServer { final List modules = []; final Uint8List codeBytes = codeFile.readAsBytesSync(); final Uint8List sourcemapBytes = sourcemapFile.readAsBytesSync(); - final Map manifest = - castStringKeyedMap(json.decode(manifestFile.readAsStringSync())); + final Map manifest = castStringKeyedMap(json.decode(manifestFile.readAsStringSync())); for (final String filePath in manifest.keys) { if (filePath == null) { globals.printTrace('Invalid manfiest file: $filePath'); continue; } - final Map offsets = - castStringKeyedMap(manifest[filePath]); - final List codeOffsets = - (offsets['code'] as List).cast(); - final List sourcemapOffsets = - (offsets['sourcemap'] as List).cast(); + final Map offsets = castStringKeyedMap(manifest[filePath]); + final List codeOffsets = (offsets['code'] as List).cast(); + final List sourcemapOffsets = (offsets['sourcemap'] as List).cast(); if (codeOffsets.length != 2 || sourcemapOffsets.length != 2) { globals.printTrace('Invalid manifest byte offsets: $offsets'); continue; @@ -236,13 +225,12 @@ class WebAssetServer { codeStart, codeEnd - codeStart, ); - _files[_filePathToUriFragment(filePath)] = byteView; + _files[filePath] = byteView; final int sourcemapStart = sourcemapOffsets[0]; final int sourcemapEnd = sourcemapOffsets[1]; if (sourcemapStart < 0 || sourcemapEnd > sourcemapBytes.lengthInBytes) { - globals - .printTrace('Invalid byte index: [$sourcemapStart, $sourcemapEnd]'); + globals.printTrace('Invalid byte index: [$sourcemapStart, $sourcemapEnd]'); continue; } final Uint8List sourcemapView = Uint8List.view( @@ -250,24 +238,132 @@ class WebAssetServer { sourcemapStart, sourcemapEnd - sourcemapStart, ); - _sourcemaps['${_filePathToUriFragment(filePath)}.map'] = sourcemapView; + _sourcemaps['$filePath.map'] = sourcemapView; modules.add(filePath); } return modules; } + + // Attempt to resolve `path` to a dart file. + File _resolveDartFile(String path) { + // If this is a dart file, it must be on the local file system and is + // likely coming from a source map request. The tool doesn't currently + // consider the case of Dart files as assets. + final File dartFile = globals.fs.file(globals.fs.currentDirectory.uri.resolve(path)); + if (dartFile.existsSync()) { + return dartFile; + } + + final List segments = path.split('/'); + if (segments.first.isEmpty) { + segments.removeAt(0); + } + + // The file might have been a package file which is signaled by a + // `/packages//` request. + if (segments.first == 'packages') { + final File packageFile = globals.fs.file(_packages.resolve(Uri( + scheme: 'package', pathSegments: segments.skip(1)))); + if (packageFile.existsSync()) { + return packageFile; + } + } + + // Otherwise it must be a Dart SDK source or a Flutter Web SDK source. + final Directory dartSdkParent = globals.fs + .directory(globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath)) + .parent; + final File dartSdkFile = globals.fs.file(globals.fs.path + .joinAll([dartSdkParent.path, ...segments])); + if (dartSdkFile.existsSync()) { + return dartSdkFile; + } + + final String flutterWebSdk = globals.artifacts + .getArtifactPath(Artifact.flutterWebSdk); + final File webSdkFile = globals.fs + .file(globals.fs.path.joinAll([flutterWebSdk, ...segments])); + + return webSdkFile; + } + + @override + Future dartSourceContents(String serverPath) { + final File result = _resolveDartFile(serverPath); + if (result.existsSync()) { + return result.readAsString(); + } + return null; + } + + @override + Future sourceMapContents(String serverPath) async { + return utf8.decode(_sourcemaps[serverPath]); + } +} + +class ConnectionResult { + ConnectionResult(this.appConnection, this.debugConnection); + + final AppConnection appConnection; + final DebugConnection debugConnection; } class WebDevFS implements DevFS { - WebDevFS(this.hostname, this.port, this._packagesFilePath); + WebDevFS({ + @required this.hostname, + @required this.port, + @required this.packagesFilePath, + @required this.urlTunneller, + @required this.buildMode, + @required this.enableDwds, + }); final String hostname; final int port; - final String _packagesFilePath; + final String packagesFilePath; + final UrlTunneller urlTunneller; + final BuildMode buildMode; + final bool enableDwds; @visibleForTesting WebAssetServer webAssetServer; + Dwds get dwds => webAssetServer.dwds; + + Future _cachedExtensionFuture; + StreamSubscription _connectedApps; + + /// Connect and retrieve the [DebugConnection] for the current application. + /// + /// Only calls [AppConnection.runMain] on the subsequent connections. + Future connect(bool useDebugExtension) { + final Completer firstConnection = Completer(); + _connectedApps = dwds.connectedApps.listen((AppConnection appConnection) async { + try { + final DebugConnection debugConnection = useDebugExtension + ? await (_cachedExtensionFuture ??= dwds.extensionDebugConnections.stream.first) + : await dwds.debugConnection(appConnection); + if (firstConnection.isCompleted) { + appConnection.runMain(); + } else { + firstConnection.complete(ConnectionResult(appConnection, debugConnection)); + } + } on Exception catch (error, stackTrace) { + if (!firstConnection.isCompleted) { + firstConnection.completeError(error, stackTrace); + } + } + }, onError: (dynamic error, StackTrace stackTrace) { + globals.printError('Unknown error while waiting for debug connection:$error\n$stackTrace'); + if (!firstConnection.isCompleted) { + firstConnection.completeError(error, stackTrace); + } + }); + return firstConnection.future; + } + @override List sources = []; @@ -283,13 +379,20 @@ class WebDevFS implements DevFS { @override Future create() async { - webAssetServer = await WebAssetServer.start(hostname, port); + webAssetServer = await WebAssetServer.start( + hostname, + port, + urlTunneller, + buildMode, + enableDwds, + ); return Uri.parse('http://$hostname:$port'); } @override Future destroy() async { await webAssetServer.dispose(); + await _connectedApps?.cancel(); } @override @@ -321,49 +424,60 @@ class WebDevFS implements DevFS { }) async { assert(trackWidgetCreation != null); assert(generator != null); + final String outputDirectoryPath = globals.fs.file(mainPath).parent.path; + if (bundleFirstUpload) { - final String entrypoint = PackageUriMapper(mainPath, '.packages', null, null) - .map(mainPath) - ?.pathSegments?.join('/'); + generator.addFileSystemRoot(outputDirectoryPath); + final String entrypoint = globals.fs.path.basename(mainPath); webAssetServer.writeFile('/manifest.json', '{"info":"manifest not generated in run mode."}'); webAssetServer.writeFile('/flutter_service_worker.js', '// Service worker not loaded in run mode.'); webAssetServer.writeFile( - '/main.dart.js', - generateBootstrapScript( - requireUrl: _filePathToUriFragment(requireJS.path), - mapperUrl: _filePathToUriFragment(stackTraceMapper.path), - entrypoint: entrypoint != null ? '/packages/$entrypoint.lib.js' : '$mainPath.lib.js', - )); + '/main.dart.js', + generateBootstrapScript( + requireUrl: _filePathToUriFragment(requireJS.path), + mapperUrl: _filePathToUriFragment(stackTraceMapper.path), + entrypoint: '/$entrypoint.lib.js', + ), + ); webAssetServer.writeFile( - '/main_module.js', - generateMainModule( - entrypoint: entrypoint != null ? '/packages/$entrypoint.lib.js' : '$mainPath.lib.js', - )); + '/main_module.bootstrap.js', + generateMainModule( + entrypoint: '/$entrypoint.lib.js', + ), + ); + // TODO(jonahwilliams): switch to DWDS provided APIs when they are ready. + webAssetServer.writeFile('/basic.digests', '{}'); webAssetServer.writeFile('/dart_sdk.js', dartSdk.readAsStringSync()); - webAssetServer.writeFile( - '/dart_sdk.js.map', dartSdkSourcemap.readAsStringSync()); + webAssetServer.writeFile('/dart_sdk.js.map', dartSdkSourcemap.readAsStringSync()); // TODO(jonahwilliams): refactor the asset code in this and the regular devfs to // be shared. if (bundle != null) { await writeBundle( - globals.fs.directory(getAssetBuildDirectory()), bundle.entries); + globals.fs.directory(getAssetBuildDirectory()), + bundle.entries, + ); } } final DateTime candidateCompileTime = DateTime.now(); if (fullRestart) { generator.reset(); } + + // The tool generates an entrypoint file in a temp directory to handle + // the web specific bootrstrap logic. To make it easier for DWDS to handle + // mapping the file name, this is done via an additional file root and + // specicial hard-coded scheme. final CompilerOutput compilerOutput = await generator.recompile( - mainPath, + 'org-dartlang-app:///' + globals.fs.path.basename(mainPath), invalidatedFiles, outputPath: dillOutputPath ?? - getDefaultApplicationKernelPath( - trackWidgetCreation: trackWidgetCreation), - packagesFilePath: _packagesFilePath, + getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation), + packagesFilePath: packagesFilePath, ); if (compilerOutput == null || compilerOutput.errorCount > 0) { return UpdateFSReport(success: false); } + // Only update the last compiled time if we successfully compiled. lastCompiled = candidateCompileTime; // list of sources that needs to be monitored are in [compilerOutput.sources] @@ -373,18 +487,19 @@ class WebDevFS implements DevFS { File sourcemapFile; List modules; try { - codeFile = globals.fs.file('${compilerOutput.outputFilename}.sources'); - manifestFile = globals.fs.file('${compilerOutput.outputFilename}.json'); - sourcemapFile = globals.fs.file('${compilerOutput.outputFilename}.map'); + final Directory parentDirectory = globals.fs.directory(outputDirectoryPath); + codeFile = parentDirectory.childFile('${compilerOutput.outputFilename}.sources'); + manifestFile = parentDirectory.childFile('${compilerOutput.outputFilename}.json'); + sourcemapFile = parentDirectory.childFile('${compilerOutput.outputFilename}.map'); modules = webAssetServer.write(codeFile, manifestFile, sourcemapFile); } on FileSystemException catch (err) { throwToolExit('Failed to load recompiled sources:\n$err'); } return UpdateFSReport( - success: true, - syncedBytes: codeFile.lengthSync(), - invalidatedSourcesCount: invalidatedFiles.length) - ..invalidatedModules = modules.map(_filePathToUriFragment).toList(); + success: true, + syncedBytes: codeFile.lengthSync(), + invalidatedSourcesCount: invalidatedFiles.length, + )..invalidatedModules = modules; } @visibleForTesting @@ -435,3 +550,43 @@ String _filePathToUriFragment(String path) { } return path; } + +class ReleaseAssetServer { + // Locations where source files, assets, or source maps may be located. + final List _searchPaths = [ + globals.fs.directory(getWebBuildDirectory()).uri, + globals.fs.directory(Cache.flutterRoot).parent.uri, + globals.fs.currentDirectory.childDirectory('lib').uri, + ]; + + Future handle(shelf.Request request) async { + Uri fileUri; + for (final Uri uri in _searchPaths) { + final Uri potential = uri.resolve(request.url.path); + if (potential == null || !globals.fs.isFileSync(potential.toFilePath())) { + continue; + } + fileUri = potential; + break; + } + + if (fileUri != null) { + final File file = globals.fs.file(fileUri); + final Uint8List bytes = file.readAsBytesSync(); + // Fallback to "application/octet-stream" on null which + // makes no claims as to the structure of the data. + final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) + ?? 'application/octet-stream'; + return shelf.Response.ok(bytes, headers: { + 'Content-Type': mimeType, + }); + } + if (request.url.path == '') { + final File file = globals.fs.file(globals.fs.path.join(getWebBuildDirectory(), 'index.html')); + return shelf.Response.ok(file.readAsBytesSync(), headers: { + 'Content-Type': 'text/html', + }); + } + return shelf.Response.notFound(''); + } +} diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index c69d3c6439a..306c31981f2 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -11,9 +11,9 @@ dependencies: # To update these, use "flutter update-packages --force-upgrade". archive: 2.0.11 args: 1.5.2 - dwds: 0.8.5 + dwds: 1.0.1 completion: 0.2.1+1 - coverage: 0.13.5 + coverage: 0.13.6 crypto: 2.1.3 file: 5.1.0 http: 0.12.0+4 @@ -94,6 +94,7 @@ dependencies: pubspec_parse: 0.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" scratch_space: 0.0.4+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf_proxy: 0.1.0+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -122,10 +123,9 @@ dev_dependencies: multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 557d +# PUBSPEC CHECKSUM: c273 diff --git a/packages/flutter_tools/test/general.shard/build_runner/multiroot_asset_reader_test.dart b/packages/flutter_tools/test/general.shard/build_runner/multiroot_asset_reader_test.dart deleted file mode 100644 index adeba5c0a46..00000000000 --- a/packages/flutter_tools/test/general.shard/build_runner/multiroot_asset_reader_test.dart +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:build/build.dart'; -import 'package:build_runner_core/build_runner_core.dart'; -import 'package:flutter_tools/src/build_runner/web_compilation_delegate.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:glob/glob.dart'; - -import '../../src/common.dart'; -import '../../src/io.dart'; -import '../../src/testbed.dart'; - -void main() { - group('MultirootFileBasedAssetReader', () { - Testbed testbed; - FakePackageGraph packageGraph; - - setUp(() { - testbed = Testbed(setup: () { - final PackageNode root = PackageNode('foobar', globals.fs.currentDirectory.path, DependencyType.path); - packageGraph = FakePackageGraph(root, {'foobar': root}); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')) - ..createSync(recursive: true) - ..writeAsStringSync('main'); - globals.fs.file(globals.fs.path.join('.dart_tool', 'build', 'generated', 'foobar', 'lib', 'bar.dart')) - ..createSync(recursive: true) - ..writeAsStringSync('bar'); - globals.fs.file('pubspec.yaml') - ..createSync() - ..writeAsStringSync('name: foobar'); - }); - }); - - test('Can find assets from the generated directory', () => testbed.run(() async { - await IOOverrides.runWithIOOverrides(() async { - final MultirootFileBasedAssetReader reader = MultirootFileBasedAssetReader( - packageGraph, - globals.fs.directory(globals.fs.path.join('.dart_tool', 'build', 'generated')), - ); - expect(await reader.canRead(AssetId('foobar', 'lib/bar.dart')), true); - expect(await reader.canRead(AssetId('foobar', 'lib/main.dart')), true); - - expect(await reader.readAsString(AssetId('foobar', 'lib/bar.dart')), 'bar'); - expect(await reader.readAsString(AssetId('foobar', 'lib/main.dart')), 'main'); - - expect(await reader.readAsBytes(AssetId('foobar', 'lib/bar.dart')), utf8.encode('bar')); - expect(await reader.readAsBytes(AssetId('foobar', 'lib/main.dart')), utf8.encode('main')); - - expect(await reader.findAssets(Glob('**')).toList(), unorderedEquals([ - AssetId('foobar', 'pubspec.yaml'), - AssetId('foobar', 'lib/bar.dart'), - AssetId('foobar', 'lib/main.dart'), - ])); - }, FlutterIOOverrides(fileSystem: globals.fs)); - // Some component of either dart:io or build_runner normalizes file uris - // into file paths for windows. This doesn't seem to work with IOOverrides - // leaving all filepaths on windows with forward slashes. - }), skip: Platform.isWindows); - }); -} - -class FakePackageGraph implements PackageGraph { - FakePackageGraph(this.root, this.allPackages); - - @override - final Map allPackages; - - @override - final PackageNode root; - - @override - PackageNode operator [](String packageName) => allPackages[packageName]; -} 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 7cf3537c504..f4431157fbf 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -15,7 +15,6 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; @@ -701,8 +700,6 @@ void main() { globals.fs.file(globals.artifacts.getArtifactPath(Artifact.webPlatformKernelDill, mode: BuildMode.debug)) .absolute.uri.toString(), ); - }, overrides: { - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), })); test('connect sets up log reader', () => testbed.run(() async { diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart index ae8f1d5c4c8..778800b9546 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart @@ -8,17 +8,16 @@ import 'package:dwds/dwds.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/terminal.dart'; -import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/build_runner/resident_web_runner.dart'; -import 'package:flutter_tools/src/build_runner/web_fs.dart'; import 'package:flutter_tools/src/web/chrome.dart'; +import 'package:flutter_tools/src/web/devfs_web.dart'; import 'package:flutter_tools/src/web/web_device.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:vm_service/vm_service.dart'; @@ -29,15 +28,22 @@ import '../src/testbed.dart'; void main() { Testbed testbed; - MockFlutterWebFs mockWebFs; ResidentWebRunner residentWebRunner; MockFlutterDevice mockFlutterDevice; + MockWebDevFS mockWebDevFS; + MockBuildSystem mockBuildSystem; setUp(() { - mockWebFs = MockFlutterWebFs(); + mockWebDevFS = MockWebDevFS(); + mockBuildSystem = MockBuildSystem(); final MockWebDevice mockWebDevice = MockWebDevice(); mockFlutterDevice = MockFlutterDevice(); when(mockFlutterDevice.device).thenReturn(mockWebDevice); + when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); + when(mockWebDevFS.sources).thenReturn([]); + when(mockBuildSystem.build(any, any)).thenAnswer((Invocation invocation) async { + return BuildResult(success: true); + }); testbed = Testbed( setup: () { residentWebRunner = residentWebRunner = DwdsWebRunnerFactory().createWebRunner( @@ -50,29 +56,14 @@ void main() { urlTunneller: null, ) as ResidentWebRunner; }, - overrides: { - WebFsFactory: () => ({ - @required String target, - @required FlutterProject flutterProject, - @required BuildInfo buildInfo, - @required bool skipDwds, - @required bool initializePlatform, - @required String hostname, - @required String port, - @required UrlTunneller urlTunneller, - @required List dartDefines, - }) async { - return mockWebFs; - }, - }, ); }); void _setupMocks() { + globals.fs.file('.packages').writeAsStringSync('\n'); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true); - when(mockWebFs.connect(any)).thenThrow(StateError('debugging not supported')); } test('Can successfully run and connect without vmservice', () => testbed.run(() async { @@ -89,6 +80,7 @@ void main() { expect(debugConnectionInfo.wsUri, null); verify(mockStatus.stop()).called(1); }, overrides: { + BuildSystem: () => mockBuildSystem, Logger: () => DelegateLogger(BufferLogger( terminal: AnsiTerminal( stdio: null, @@ -105,12 +97,11 @@ void main() { connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return true; - }); final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 0); + }, overrides: { + BuildSystem: () => mockBuildSystem, })); test('Fails on compilation errors in hot restart', () => testbed.run(() async { @@ -120,13 +111,15 @@ void main() { connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return false; + when(mockBuildSystem.build(any, any)).thenAnswer((Invocation invocation) async { + return BuildResult(success: false); }); final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 1); expect(result.message, contains('Failed to recompile application.')); + }, overrides: { + BuildSystem: () => mockBuildSystem, })); test('Correctly performs a full refresh on attached chrome device.', () => testbed.run(() async { @@ -150,22 +143,20 @@ void main() { connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return true; - }); final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 0); verify(mockWipConnection.sendCommand('Page.reload', { 'ignoreCache': true, })).called(1); + }, overrides: { + BuildSystem: () => mockBuildSystem, })); } +class MockWebDevFS extends Mock implements WebDevFS {} class MockWebDevice extends Mock implements Device {} -class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {} -class MockFlutterWebFs extends Mock implements WebFs {} class MockDebugConnection extends Mock implements DebugConnection {} class MockVmService extends Mock implements VmService {} class MockStatus extends Mock implements Status {} @@ -175,3 +166,4 @@ class MockChrome extends Mock implements Chrome {} class MockChromeConnection extends Mock implements ChromeConnection {} class MockChromeTab extends Mock implements ChromeTab {} class MockWipConnection extends Mock implements WipConnection {} +class MockBuildSystem extends Mock implements BuildSystem {} 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 c4a3edaff93..ea558eaf92e 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 @@ -5,27 +5,22 @@ import 'dart:async'; import 'dart:convert'; -import 'package:build_daemon/client.dart'; import 'package:dwds/dwds.dart'; import 'package:flutter_tools/src/base/common.dart'; -import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_runner/resident_web_runner.dart'; -import 'package:flutter_tools/src/build_runner/web_fs.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/web/chrome.dart'; +import 'package:flutter_tools/src/web/devfs_web.dart'; import 'package:flutter_tools/src/web/web_device.dart'; -import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:vm_service/vm_service.dart'; @@ -37,7 +32,6 @@ import '../src/testbed.dart'; void main() { Testbed testbed; - MockFlutterWebFs mockWebFs; ResidentWebRunner residentWebRunner; MockDebugConnection mockDebugConnection; MockVmService mockVmService; @@ -52,14 +46,13 @@ void main() { MockWipConnection mockWipConnection; MockWipDebugger mockWipDebugger; MockWebServerDevice mockWebServerDevice; - bool didSkipDwds; + MockDevice mockDevice; setUp(() { resetChromeForTesting(); - mockWebFs = MockFlutterWebFs(); mockDebugConnection = MockDebugConnection(); mockVmService = MockVmService(); - mockChromeDevice = MockChromeDevice(); + mockDevice = MockDevice(); mockAppConnection = MockAppConnection(); mockFlutterDevice = MockFlutterDevice(); mockWebDevFS = MockWebDevFS(); @@ -70,7 +63,11 @@ void main() { mockWipConnection = MockWipConnection(); mockWipDebugger = MockWipDebugger(); mockWebServerDevice = MockWebServerDevice(); - when(mockFlutterDevice.device).thenReturn(mockChromeDevice); + when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); + when(mockFlutterDevice.device).thenReturn(mockDevice); + when(mockWebDevFS.connect(any)).thenAnswer((Invocation invocation) async { + return ConnectionResult(mockAppConnection, mockDebugConnection); + }); testbed = Testbed( setup: () { residentWebRunner = DwdsWebRunnerFactory().createWebRunner( @@ -85,22 +82,6 @@ void main() { globals.fs.currentDirectory.childFile('.packages') ..writeAsStringSync('\n'); }, - overrides: { - WebFsFactory: () => ({ - @required String target, - @required FlutterProject flutterProject, - @required BuildInfo buildInfo, - @required bool skipDwds, - @required bool initializePlatform, - @required String hostname, - @required String port, - @required List dartDefines, - @required UrlTunneller urlTunneller, - }) async { - didSkipDwds = skipDwds; - return mockWebFs; - }, - }, ); }); @@ -108,13 +89,22 @@ void main() { globals.fs.file('pubspec.yaml').createSync(); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - return ConnectionResult(mockAppConnection, mockDebugConnection); + when(mockWebDevFS.update( + mainPath: anyNamed('mainPath'), + target: anyNamed('target'), + bundle: anyNamed('bundle'), + firstBuildTime: anyNamed('firstBuildTime'), + bundleFirstUpload: anyNamed('bundleFirstUpload'), + generator: anyNamed('generator'), + fullRestart: anyNamed('fullRestart'), + dillOutputPath: anyNamed('dillOutputPath'), + projectRootPath: anyNamed('projectRootPath'), + pathToReload: anyNamed('pathToReload'), + invalidatedFiles: anyNamed('invalidatedFiles'), + trackWidgetCreation: true, + )).thenAnswer((Invocation _) async { + return UpdateFSReport(success: true, syncedBytes: 0)..invalidatedModules = []; }); - when(mockWebFs.recompile()).thenAnswer((Invocation _) { - return Future.value(false); - }); - when(mockWebFs.uri).thenReturn('http://localhost:8765/app/'); when(mockDebugConnection.vmService).thenReturn(mockVmService); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { return Completer().future; @@ -157,19 +147,6 @@ void main() { expect(residentWebRunner.debuggingEnabled, true); })); - test('runner with web server device does not initialize dwds', () => testbed.run(() async { - _setupMocks(); - when(mockFlutterDevice.device).thenReturn(WebServerDevice()); - - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - - expect(didSkipDwds, true); - })); - test('runner with web server device supports debugging with --start-paused', () => testbed.run(() { when(mockFlutterDevice.device).thenReturn(WebServerDevice()); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( @@ -185,33 +162,8 @@ void main() { expect(profileResidentWebRunner.debuggingEnabled, true); })); - test('runner with web server device uses debug extension with --start-paused', () => testbed.run(() async { - _setupMocks(); - when(mockFlutterDevice.device).thenReturn(WebServerDevice()); - final ResidentWebRunner runner = DwdsWebRunnerFactory().createWebRunner( - mockFlutterDevice, - flutterProject: FlutterProject.current(), - debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true), - ipv6: true, - stayResident: true, - dartDefines: [], - urlTunneller: null, - ) as ResidentWebRunner; - - final Completer connectionInfoCompleter = Completer(); - unawaited(runner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - - // Check connect() was told to use the debug extension. - verify(mockWebFs.connect(true)).called(1); - // And ensure the debug services was started. - expect(testLogger.statusText, contains('Debug service listening on')); - })); - test('profile does not supportsServiceProtocol', () => testbed.run(() { - when(mockFlutterDevice.device).thenReturn(mockChromeDevice); + when(mockFlutterDevice.device).thenReturn(mockChromeDevice); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), @@ -256,7 +208,7 @@ void main() { verify(mockAppConnection.runMain()).called(1); verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1); - verify(status.stop()).called(2); + verify(status.stop()).called(1); expect(bufferLogger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/')); expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/'); @@ -333,37 +285,6 @@ void main() { })); test('Can hot reload after attaching', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation invocation) async { - return true; - }); - when(mockVmService.callServiceExtension('hotRestart')).thenAnswer((Invocation _) async { - return Response.parse({'type': 'Success'}); - }); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); - - expect(testLogger.statusText, contains('Reloaded application in')); - expect(result.code, 0); - // ensure that analytics are sent. - verify(Usage.instance.sendEvent('hot', 'restart', parameters: { - 'cd27': 'web-javascript', - 'cd28': null, - 'cd29': 'false', - 'cd30': 'true', - })).called(1); - verify(Usage.instance.sendTiming('hot', 'web-restart', any)).called(1); - verify(Usage.instance.sendTiming('hot', 'web-refresh', any)).called(1); - verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1); - }, overrides: { - Usage: () => MockFlutterUsage(), - })); - - test('Can hot reload after attaching - experimental', () => testbed.run(() async { _setupMocks(); launchChromeInstance(mockChrome); when(mockWebDevFS.update( @@ -399,19 +320,21 @@ void main() { expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. - verify(Usage.instance.sendEvent('hot', 'restart', parameters: { - 'cd27': 'web-javascript', - 'cd28': null, - 'cd29': 'false', - 'cd30': 'true', - })).called(1); + final Map config = verify(Usage.instance.sendEvent('hot', 'restart', + parameters: captureAnyNamed('parameters'))).captured.first as Map; + + expect(config, allOf([ + containsPair('cd27', 'web-javascript'), + containsPair('cd28', null), + containsPair('cd29', 'false'), + containsPair('cd30', 'true'), + ])); verify(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)).called(1); }, overrides: { Usage: () => MockFlutterUsage(), - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), })); - test('Can hot restart after attaching - experimental', () => testbed.run(() async { + test('Can hot restart after attaching', () => testbed.run(() async { _setupMocks(); launchChromeInstance(mockChrome); String entrypointFileName; @@ -450,19 +373,21 @@ void main() { expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. - verify(Usage.instance.sendEvent('hot', 'restart', parameters: { - 'cd27': 'web-javascript', - 'cd28': null, - 'cd29': 'false', - 'cd30': 'true', - })).called(1); - verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)); + final Map config = verify(Usage.instance.sendEvent('hot', 'restart', + parameters: captureAnyNamed('parameters'))).captured.first as Map; + + expect(config, allOf([ + containsPair('cd27', 'web-javascript'), + containsPair('cd28', null), + containsPair('cd29', 'false'), + containsPair('cd30', 'true'), + ])); + verify(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)).called(1); }, overrides: { Usage: () => MockFlutterUsage(), - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), })); - test('Can hot restart after attaching - experimental with web-server device', () => testbed.run(() async { + test('Can hot restart after attaching with web-server device', () => testbed.run(() async { _setupMocks(); when(mockFlutterDevice.device).thenReturn(mockWebServerDevice); when(mockWebDevFS.update( @@ -493,71 +418,45 @@ void main() { expect(result.code, 0); verify(mockResidentCompiler.accept()).called(2); // ensure that analytics are sent. - verify(Usage.instance.sendEvent('hot', 'restart', parameters: { - 'cd27': 'web-javascript', - 'cd28': null, - 'cd29': 'false', - 'cd30': 'true', - })).called(1); verifyNever(Usage.instance.sendTiming('hot', 'web-incremental-restart', any)); }, overrides: { Usage: () => MockFlutterUsage(), - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), })); - test('experimental resident runner is not debuggable', () => testbed.run(() { - expect(residentWebRunner.debuggingEnabled, false); + test('web resident runner iss debuggable', () => testbed.run(() { + expect(residentWebRunner.debuggingEnabled, true); }, overrides: { - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), })); - test('Can hot restart after attaching', () => testbed.run(() async { + test('Exits when initial compile fails', () => testbed.run(() async { _setupMocks(); + when(mockWebDevFS.update( + mainPath: anyNamed('mainPath'), + target: anyNamed('target'), + bundle: anyNamed('bundle'), + firstBuildTime: anyNamed('firstBuildTime'), + bundleFirstUpload: anyNamed('bundleFirstUpload'), + generator: anyNamed('generator'), + fullRestart: anyNamed('fullRestart'), + dillOutputPath: anyNamed('dillOutputPath'), + projectRootPath: anyNamed('projectRootPath'), + pathToReload: anyNamed('pathToReload'), + invalidatedFiles: anyNamed('invalidatedFiles'), + trackWidgetCreation: true, + )).thenAnswer((Invocation _) async { + return UpdateFSReport(success: false, syncedBytes: 0)..invalidatedModules = []; + }); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); - await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation invocation) async { - return true; - }); - when(mockVmService.callServiceExtension('fullReload')).thenAnswer((Invocation _) async { - return Response.parse({'type': 'Success'}); - }); - final OperationResult result = await residentWebRunner.restart(fullRestart: true); - expect(testLogger.statusText, contains('Restarted application in')); - expect(result.code, 0); - // ensure that analytics are sent. - verify(Usage.instance.sendEvent('hot', 'restart', parameters: { - 'cd27': 'web-javascript', - 'cd28': null, - 'cd29': 'false', - 'cd30': 'true', - })).called(1); + expect(await residentWebRunner.run(), 1); verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any)); - verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any)); - verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1); }, overrides: { Usage: () => MockFlutterUsage(), })); - test('Selects Dwds runner in profile mode with incremental compiler enabled', () => testbed.run(() async { - final ResidentWebRunner residentWebRunner = DwdsWebRunnerFactory().createWebRunner( - mockFlutterDevice, - flutterProject: FlutterProject.current(), - debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile), - ipv6: true, - stayResident: true, - dartDefines: const [], - urlTunneller: null, - ) as ResidentWebRunner; - - expect(residentWebRunner.runtimeType.toString(), '_DwdsResidentWebRunner'); - }, overrides: { - FeatureFlags: () => TestFeatureFlags(isWebIncrementalCompilerEnabled: true), - })); - test('Fails on compilation errors in hot restart', () => testbed.run(() async { _setupMocks(); final Completer connectionInfoCompleter = Completer(); @@ -565,84 +464,80 @@ void main() { connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return false; + when(mockWebDevFS.update( + mainPath: anyNamed('mainPath'), + target: anyNamed('target'), + bundle: anyNamed('bundle'), + firstBuildTime: anyNamed('firstBuildTime'), + bundleFirstUpload: anyNamed('bundleFirstUpload'), + generator: anyNamed('generator'), + fullRestart: anyNamed('fullRestart'), + dillOutputPath: anyNamed('dillOutputPath'), + projectRootPath: anyNamed('projectRootPath'), + pathToReload: anyNamed('pathToReload'), + invalidatedFiles: anyNamed('invalidatedFiles'), + trackWidgetCreation: true, + )).thenAnswer((Invocation _) async { + return UpdateFSReport(success: false, syncedBytes: 0)..invalidatedModules = []; }); + final OperationResult result = await residentWebRunner.restart(fullRestart: true); expect(result.code, 1); expect(result.message, contains('Failed to recompile application.')); verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any)); - verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any)); - verifyNever(Usage.instance.sendTiming('hot', 'web-recompile', any)); }, overrides: { Usage: () => MockFlutterUsage(), })); - test('Fails on vmservice response error for hot restart', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return true; - }); - when(mockVmService.callServiceExtension('fullReload')).thenAnswer((Invocation _) async { - return Response.parse({'type': 'Failed'}); - }); - final OperationResult result = await residentWebRunner.restart(fullRestart: true); + // TODO(jonahwilliams): re-enable tests once we switch back to DWDS for hot reload/restart. + // test('Fails on vmservice response error for hot restart', () => testbed.run(() async { + // _setupMocks(); + // final Completer connectionInfoCompleter = Completer(); + // unawaited(residentWebRunner.run( + // connectionInfoCompleter: connectionInfoCompleter, + // )); + // await connectionInfoCompleter.future; + // when(mockVmService.callServiceExtension('fullReload')).thenAnswer((Invocation _) async { + // return Response.parse({'type': 'Failed'}); + // }); + // final OperationResult result = await residentWebRunner.restart(fullRestart: true); - expect(result.code, 1); - expect(result.message, contains('Failed')); - verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any)); - verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any)); - verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1); - }, overrides: { - Usage: () => MockFlutterUsage(), - })); + // expect(result.code, 1); + // expect(result.message, contains('Failed')); + // })); - test('Fails on vmservice response error for hot reload', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return true; - }); - when(mockVmService.callServiceExtension('hotRestart')).thenAnswer((Invocation _) async { - return Response.parse({'type': 'Failed'}); - }); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); + // TODO(jonahwilliams): re-enable tests once we switch back to DWDS for hot reload/restart. + // test('Fails on vmservice response error for hot reload', () => testbed.run(() async { + // _setupMocks(); + // final Completer connectionInfoCompleter = Completer(); + // unawaited(residentWebRunner.run( + // connectionInfoCompleter: connectionInfoCompleter, + // )); + // await connectionInfoCompleter.future; + // when(mockVmService.callServiceExtension('hotRestart')).thenAnswer((Invocation _) async { + // return Response.parse({'type': 'Failed'}); + // }); + // final OperationResult result = await residentWebRunner.restart(fullRestart: false); - expect(result.code, 1); - expect(result.message, contains('Failed')); - verifyNever(Usage.instance.sendTiming('hot', 'web-restart', any)); - verifyNever(Usage.instance.sendTiming('hot', 'web-refresh', any)); - verify(Usage.instance.sendTiming('hot', 'web-recompile', any)).called(1); - }, overrides: { - Usage: () => MockFlutterUsage(), - })); + // expect(result.code, 1); + // expect(result.message, contains('Failed')); + // })); - test('Fails on vmservice RpcError', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockWebFs.recompile()).thenAnswer((Invocation _) async { - return true; - }); - when(mockVmService.callServiceExtension('hotRestart')).thenThrow(RPCError('', 2, '123')); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); + // TODO(jonahwilliams): re-enable tests once we switch back to DWDS for hot reload/restart. + // test('Fails on vmservice RpcError', () => testbed.run(() async { + // _setupMocks(); + // final Completer connectionInfoCompleter = Completer(); + // unawaited(residentWebRunner.run( + // connectionInfoCompleter: connectionInfoCompleter, + // )); + // await connectionInfoCompleter.future; + // when(mockVmService.callServiceExtension('hotRestart')).thenThrow(RPCError('', 2, '123')); + // final OperationResult result = await residentWebRunner.restart(fullRestart: false); - expect(result.code, 1); - expect(result.message, contains('Page requires refresh')); - })); + // expect(result.code, 1); + // expect(result.message, contains('Page requires refresh')); + // })); test('printHelp without details has web warning', () => testbed.run(() async { residentWebRunner.printHelp(details: false); @@ -806,7 +701,7 @@ void main() { test('cleanup of resources is safe to call multiple times', () => testbed.run(() async { _setupMocks(); bool debugClosed = false; - when(mockChromeDevice.stopApp(any)).thenAnswer((Invocation invocation) async { + when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async { if (debugClosed) { throw StateError('debug connection closed twice'); } @@ -839,12 +734,11 @@ void main() { onDone.complete(); await result; - verify(mockWebFs.stop()).called(1); })); test('Prints target and device name on run', () => testbed.run(() async { _setupMocks(); - when(mockChromeDevice.name).thenReturn('Chromez'); + when(mockDevice.name).thenReturn('Chromez'); final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, @@ -857,6 +751,21 @@ void main() { test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async { _setupMocks(); when(mockFlutterDevice.device).thenReturn(ChromeDevice()); + when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { + return Uri.parse('http://localhost:8765/app/'); + }); + final MockChrome chrome = MockChrome(); + final MockChromeConnection mockChromeConnection = MockChromeConnection(); + final MockChromeTab mockChromeTab = MockChromeTab(); + final MockWipConnection mockWipConnection = MockWipConnection(); + when(mockChromeConnection.getTab(any)).thenAnswer((Invocation invocation) async { + return mockChromeTab; + }); + when(mockChromeTab.connect()).thenAnswer((Invocation invocation) async { + return mockWipConnection; + }); + when(chrome.chromeConnection).thenReturn(mockChromeConnection); + launchChromeInstance(chrome); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); @@ -893,6 +802,9 @@ void main() { test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async { _setupMocks(); when(mockFlutterDevice.device).thenReturn(WebServerDevice()); + when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { + return Uri.parse('http://localhost:8765/app/'); + }); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); @@ -924,203 +836,17 @@ void main() { }, overrides: { Logger: () => DelegateLogger(MockLogger()) })); - - test('Successfully turns WebSocketException into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw const WebSocketException(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns AppConnectionException into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw AppConnectionException('Could not connect to application with appInstanceId: c0ae0750-ee91-11e9-cea6-35d95a968356'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns ChromeDebugError into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw ChromeDebugException({}); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns OptionsSkew error into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw OptionsSkew(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns VersionSkew error into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw VersionSkew(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns failed startup StateError error into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw StateError('Unable to start build daemon'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - - test('Rethrows Exception type', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw Exception('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsException); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Successfully turns MissingPortFile error into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw MissingPortFile(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Rethrows unknown exception type from web tooling', () => testbed.run(() async { - _setupMocks(); - final DelegateLogger delegateLogger = globals.logger as DelegateLogger; - final MockStatus mockStatus = MockStatus(); - delegateLogger.status = mockStatus; - final Completer connectionInfoCompleter = Completer(); - final Completer unhandledErrorCompleter = Completer(); - when(mockWebFs.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw StateError('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsStateError); - - unhandledErrorCompleter.complete(); - await expectation; - verify(mockStatus.stop()).called(2); - }, overrides: { - Logger: () => DelegateLogger(BufferLogger( - terminal: AnsiTerminal( - stdio: null, - platform: const LocalPlatform(), - ), - outputPreferences: OutputPreferences.test(), - )) - })); } class MockChromeLauncher extends Mock implements ChromeLauncher {} class MockFlutterUsage extends Mock implements Usage {} class MockChromeDevice extends Mock implements ChromeDevice {} -class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {} -class MockFlutterWebFs extends Mock implements WebFs {} class MockDebugConnection extends Mock implements DebugConnection {} class MockAppConnection extends Mock implements AppConnection {} class MockVmService extends Mock implements VmService {} class MockStatus extends Mock implements Status {} class MockFlutterDevice extends Mock implements FlutterDevice {} -class MockWebDevFS extends Mock implements DevFS {} +class MockWebDevFS extends Mock implements WebDevFS {} class MockResidentCompiler extends Mock implements ResidentCompiler {} class MockChrome extends Mock implements Chrome {} class MockChromeConnection extends Mock implements ChromeConnection {} @@ -1129,3 +855,4 @@ class MockWipConnection extends Mock implements WipConnection {} class MockWipDebugger extends Mock implements WipDebugger {} class MockLogger extends Mock implements Logger {} class MockWebServerDevice extends Mock implements WebServerDevice {} +class MockDevice extends Mock implements Device {} diff --git a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart index 07c86e67640..0e9eeb644b7 100644 --- a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart +++ b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart @@ -18,7 +18,7 @@ void main() { // stack trace mapper source is interpolated correctly. expect(result, contains('mapperEl.src = "mapper.js";')); // data-main is set to correct bootstrap module. - expect(result, contains('requireEl.setAttribute("data-main", "main_module");')); + expect(result, contains('requireEl.setAttribute("data-main", "main_module.bootstrap");')); // bootstrap main module has correct imports. expect(result, contains('require(["foo/bar/main.js", "dart_sdk"],' ' function(app, dart_sdk) {')); @@ -29,7 +29,7 @@ void main() { entrypoint: 'foo/bar/main.js', ); // bootstrap main module has correct defined module. - expect(result, contains('define("main_module", ["foo/bar/main.js", "dart_sdk"], ' + expect(result, contains('define("main_module.bootstrap", ["foo/bar/main.js", "dart_sdk"], ' 'function(app, dart_sdk) {')); }); } diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index 158d99a3ef9..d2d31df70d5 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; import 'dart:io'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/web/devfs_web.dart'; @@ -15,8 +15,10 @@ import 'package:package_config/discovery.dart'; import 'package:package_config/packages.dart'; import 'package:platform/platform.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:shelf/shelf.dart'; import '../../src/common.dart'; +import '../../src/io.dart'; import '../../src/testbed.dart'; const List kTransparentImage = [ @@ -28,75 +30,26 @@ const List kTransparentImage = [ ]; void main() { - MockHttpServer mockHttpServer; - StreamController requestController; Testbed testbed; - MockHttpRequest request; - MockHttpResponse response; - MockHttpHeaders headers; - Completer closeCompleter; WebAssetServer webAssetServer; - MockPlatform linux; + Platform linux; Packages packages; - MockPlatform windows; + Platform windows; + MockHttpServer mockHttpServer; setUpAll(() async { - packages = await loadPackagesFile(Uri.base.resolve('.packages'), loader: (Uri uri) async { - return utf8.encode('\n'); - }); + packages = await loadPackagesFile(Uri.base.resolve('.packages')); }); setUp(() { - linux = MockPlatform(); - windows = MockPlatform(); - when(linux.isWindows).thenReturn(false); - when(linux.environment).thenReturn(const {}); - when(windows.environment).thenReturn(const {}); - when(windows.isWindows).thenReturn(true); + mockHttpServer = MockHttpServer(); + linux = FakePlatform(operatingSystem: 'linux', environment: {}); + windows = FakePlatform(operatingSystem: 'windows', environment: {}); testbed = Testbed(setup: () { - mockHttpServer = MockHttpServer(); - requestController = StreamController.broadcast(); - request = MockHttpRequest(); - response = MockHttpResponse(); - headers = MockHttpHeaders(); - closeCompleter = Completer(); - when(mockHttpServer.listen(any, onError: anyNamed('onError'))).thenAnswer((Invocation invocation) { - final void Function(HttpRequest) callback = invocation.positionalArguments.first as void Function(HttpRequest); - return requestController.stream.listen(callback); - }); - when(request.response).thenReturn(response); - when(response.headers).thenReturn(headers); - when(response.close()).thenAnswer((Invocation invocation) async { - closeCompleter.complete(); - }); - webAssetServer = WebAssetServer( - mockHttpServer, packages, InternetAddress.loopbackIPv4, onError: (dynamic error, StackTrace stackTrace) { - closeCompleter.completeError(error, stackTrace); - }); + webAssetServer = WebAssetServer(mockHttpServer, packages, InternetAddress.loopbackIPv4); }); }); - tearDown(() async { - await webAssetServer.dispose(); - await requestController.close(); - }); - - test('Throws a tool exit if bind fails with a SocketException', () => testbed.run(() async { - expect(WebAssetServer.start('hello', 1234), throwsToolExit()); - })); - - test('Can catch exceptions through the onError callback', () => testbed.run(() async { - when(response.close()).thenAnswer((Invocation invocation) { - throw StateError('Something bad'); - }); - webAssetServer.writeFile('/foo.js', 'main() {}'); - - when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js')); - requestController.add(request); - - expect(closeCompleter.future, throwsStateError); - })); - test('Handles against malformed manifest', () => testbed.run(() async { final File source = globals.fs.file('source') ..writeAsStringSync('main() {}'); @@ -131,13 +84,14 @@ void main() { }})); webAssetServer.write(source, manifest, sourcemap); - when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js'))); - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'application/javascript')).called(1); - verify(response.add(source.readAsBytesSync())).called(1); + expect(response.headers, allOf([ + containsPair('content-length', source.lengthSync().toString()), + containsPair('content-type', 'application/javascript'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); }, overrides: { Platform: () => linux, })); @@ -145,13 +99,14 @@ void main() { test('serves JavaScript files from in memory cache not from manifest', () => testbed.run(() async { webAssetServer.writeFile('/foo.js', 'main() {}'); - when(request.uri).thenReturn(Uri.parse('http://foobar/foo.js')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/foo.js'))); - verify(headers.add('Content-Length', 9)).called(1); - verify(headers.add('Content-Type', 'application/javascript')).called(1); - verify(response.add(any)).called(1); + expect(response.headers, allOf([ + containsPair('content-length', '9'), + containsPair('content-type', 'application/javascript'), + ])); + expect((await response.read().toList()).first, utf8.encode('main() {}')); })); test('handles missing JavaScript files from in memory cache', () => testbed.run(() async { @@ -166,11 +121,10 @@ void main() { }})); webAssetServer.write(source, manifest, sourcemap); - when(request.uri).thenReturn(Uri.parse('http://foobar/bar.js')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/bar.js'))); - verify(response.statusCode = 404).called(1); + expect(response.statusCode, HttpStatus.notFound); })); test('serves JavaScript files from in memory cache on Windows', () => testbed.run(() async { @@ -179,50 +133,35 @@ void main() { final File sourcemap = globals.fs.file('sourcemap') ..writeAsStringSync('{}'); final File manifest = globals.fs.file('manifest') - ..writeAsStringSync(json.encode({'/C:/foo.js': { + ..writeAsStringSync(json.encode({'/foo.js': { 'code': [0, source.lengthSync()], 'sourcemap': [0, 2], }})); webAssetServer.write(source, manifest, sourcemap); + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://localhost/foo.js'))); - when(request.uri).thenReturn(Uri.parse('http://foobar/C:/foo.js')); - requestController.add(request); - await closeCompleter.future; - - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'application/javascript')).called(1); - verify(response.add(source.readAsBytesSync())).called(1); + expect(response.headers, allOf([ + containsPair('content-length', source.lengthSync().toString()), + containsPair('content-type', 'application/javascript'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); }, overrides: { Platform: () => windows, })); - test('serves Dart files from in filesystem on Windows', () => testbed.run(() async { - final File source = globals.fs.file('foo.dart').absolute - ..createSync(recursive: true) - ..writeAsStringSync('void main() {}'); - - when(request.uri).thenReturn(Uri.parse('http://foobar/C:/foo.dart')); - requestController.add(request); - await closeCompleter.future; - - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(response.addStream(any)).called(1); - }, overrides: { - Platform: () => windows, - })); - test('serves asset files from in filesystem with known mime type on Windows', () => testbed.run(() async { final File source = globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'foo.png')) ..createSync(recursive: true) ..writeAsBytesSync(kTransparentImage); + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png'))); - when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo.png')); - requestController.add(request); - await closeCompleter.future; - - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'image/png')).called(1); - verify(response.addStream(any)).called(1); + expect(response.headers, allOf([ + containsPair('content-length', source.lengthSync().toString()), + containsPair('content-type', 'image/png'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); }, overrides: { Platform: () => windows, })); @@ -232,22 +171,20 @@ void main() { ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); - when(request.uri).thenReturn(Uri.parse('http://foobar/foo.dart')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart'))); - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(response.addStream(any)).called(1); + expect(response.headers, containsPair('content-length', source.lengthSync().toString())); + expect((await response.read().toList()).first, source.readAsBytesSync()); }, overrides: { Platform: () => linux, })); test('Handles missing Dart files from filesystem', () => testbed.run(() async { - when(request.uri).thenReturn(Uri.parse('http://foobar/foo.dart')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/foo.dart'))); - verify(response.statusCode = 404).called(1); + expect(response.statusCode, HttpStatus.notFound); })); test('serves asset files from in filesystem with known mime type', () => testbed.run(() async { @@ -255,13 +192,14 @@ void main() { ..createSync(recursive: true) ..writeAsBytesSync(kTransparentImage); - when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo.png')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo.png'))); - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'image/png')).called(1); - verify(response.addStream(any)).called(1); + expect(response.headers, allOf([ + containsPair('content-length', source.lengthSync().toString()), + containsPair('content-type', 'image/png'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); })); test('serves asset files files from in filesystem with unknown mime type and length > 12', () => testbed.run(() async { @@ -269,13 +207,14 @@ void main() { ..createSync(recursive: true) ..writeAsBytesSync(List.filled(100, 0)); - when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo'))); - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'application/octet-stream')).called(1); - verify(response.addStream(any)).called(1); + expect(response.headers, allOf([ + containsPair('content-length', '100'), + containsPair('content-type', 'application/octet-stream'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); })); test('serves asset files files from in filesystem with unknown mime type and length < 12', () => testbed.run(() async { @@ -283,21 +222,21 @@ void main() { ..createSync(recursive: true) ..writeAsBytesSync([1, 2, 3]); - when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo'))); - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'application/octet-stream')).called(1); - verify(response.addStream(any)).called(1); + expect(response.headers, allOf([ + containsPair('content-length', '3'), + containsPair('content-type', 'application/octet-stream'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); })); test('handles serving missing asset file', () => testbed.run(() async { - when(request.uri).thenReturn(Uri.parse('http://foobar/assets/foo')); - requestController.add(request); - await closeCompleter.future; + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http://foobar/assets/foo'))); - verify(response.statusCode = HttpStatus.notFound).called(1); + expect(response.statusCode, HttpStatus.notFound); })); test('serves /packages// files as if they were ' @@ -307,14 +246,15 @@ void main() { final File source = globals.fs.file(globals.fs.path.fromUri(expectedUri)) ..createSync(recursive: true) ..writeAsBytesSync([1, 2, 3]); - when(request.uri).thenReturn( - Uri.parse('http:///packages/flutter_tools/foo.dart')); - requestController.add(request); - await closeCompleter.future; - verify(headers.add('Content-Length', source.lengthSync())).called(1); - verify(headers.add('Content-Type', 'application/octet-stream')).called(1); - verify(response.addStream(any)).called(1); + final Response response = await webAssetServer + .handleRequest(Request('GET', Uri.parse('http:///packages/flutter_tools/foo.dart'))); + + expect(response.headers, allOf([ + containsPair('content-length', '3'), + containsPair('content-type', 'application/octet-stream'), + ])); + expect((await response.read().toList()).first, source.readAsBytesSync()); })); test('calling dispose closes the http server', () => testbed.run(() async { @@ -324,46 +264,54 @@ void main() { })); test('Can start web server with specified assets', () => testbed.run(() async { - globals.fs.file('a.sources').writeAsStringSync(''); - globals.fs.file('a.json').writeAsStringSync('{}'); - globals.fs.file('a.map').writeAsStringSync('{}'); - globals.fs.file('.packages').writeAsStringSync('\n'); + await IOOverrides.runWithIOOverrides(() async { + final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart')) + ..createSync(recursive: true); + outputFile.parent.childFile('a.sources').writeAsStringSync(''); + outputFile.parent.childFile('a.json').writeAsStringSync('{}'); + outputFile.parent.childFile('a.map').writeAsStringSync('{}'); + outputFile.parent.childFile('.packages').writeAsStringSync('\n'); - final ResidentCompiler residentCompiler = MockResidentCompiler(); - when(residentCompiler.recompile( - any, - any, - outputPath: anyNamed('outputPath'), - packagesFilePath: anyNamed('packagesFilePath'), - )).thenAnswer((Invocation invocation) async { - return const CompilerOutput('a', 0, []); - }); + final ResidentCompiler residentCompiler = MockResidentCompiler(); + when(residentCompiler.recompile( + any, + any, + outputPath: anyNamed('outputPath'), + packagesFilePath: anyNamed('packagesFilePath'), + )).thenAnswer((Invocation invocation) async { + return const CompilerOutput('a', 0, []); + }); - final WebDevFS webDevFS = WebDevFS('localhost', 0, '.packages'); - webDevFS.requireJS.createSync(recursive: true); - webDevFS.dartSdk.createSync(recursive: true); - webDevFS.dartSdkSourcemap.createSync(recursive: true); - webDevFS.stackTraceMapper.createSync(recursive: true); + final WebDevFS webDevFS = WebDevFS( + hostname: 'localhost', + port: 0, + packagesFilePath: '.packages', + urlTunneller: null, + buildMode: BuildMode.debug, + enableDwds: false, + ); + webDevFS.requireJS.createSync(recursive: true); + webDevFS.dartSdk.createSync(recursive: true); + webDevFS.dartSdkSourcemap.createSync(recursive: true); + webDevFS.stackTraceMapper.createSync(recursive: true); - await webDevFS.create(); - await webDevFS.update( - mainPath: globals.fs.path.join('lib', 'main.dart'), - generator: residentCompiler, - trackWidgetCreation: true, - bundleFirstUpload: true, - invalidatedFiles: [], - ); + await webDevFS.create(); + await webDevFS.update( + mainPath: globals.fs.path.join('lib', 'main.dart'), + generator: residentCompiler, + trackWidgetCreation: true, + bundleFirstUpload: true, + invalidatedFiles: [], + ); - expect(webDevFS.webAssetServer.getFile('/manifest.json'), isNotNull); - expect(webDevFS.webAssetServer.getFile('/flutter_service_worker.js'), isNotNull); + expect(webDevFS.webAssetServer.getFile('/manifest.json'), isNotNull); + expect(webDevFS.webAssetServer.getFile('/flutter_service_worker.js'), isNotNull); - await webDevFS.destroy(); - })); + await webDevFS.destroy(); + await webDevFS.dwds.stop(); + }, FlutterIOOverrides(fileSystem: globals.fs)); + }), skip: true); // Not clear the best way to test this, since shelf hits the real filesystem. } class MockHttpServer extends Mock implements HttpServer {} -class MockHttpRequest extends Mock implements HttpRequest {} -class MockHttpResponse extends Mock implements HttpResponse {} -class MockHttpHeaders extends Mock implements HttpHeaders {} -class MockPlatform extends Mock implements Platform {} class MockResidentCompiler extends Mock implements ResidentCompiler {} diff --git a/packages/flutter_tools/test/general.shard/web/asset_server_test.dart b/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart similarity index 54% rename from packages/flutter_tools/test/general.shard/web/asset_server_test.dart rename to packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart index d964a5b4222..5e9a39cd093 100644 --- a/packages/flutter_tools/test/general.shard/web/asset_server_test.dart +++ b/packages/flutter_tools/test/general.shard/web/web_asset_server_test.dart @@ -2,12 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/build_runner/web_fs.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/web/devfs_web.dart'; import 'package:shelf/shelf.dart'; import '../../src/common.dart'; @@ -23,7 +20,6 @@ const List kTransparentImage = [ void main() { Testbed testbed; - AssetServer assetServer; setUp(() { testbed = Testbed( @@ -39,80 +35,12 @@ void main() { globals.fs.file(globals.fs.path.join('build', 'flutter_assets', 'bar')) ..createSync(recursive: true) ..writeAsBytesSync([1, 2, 3]); - assetServer = DebugAssetServer(FlutterProject.current(), globals.fs.path.join('main')); } ); }); - test('can serve an html file from the web directory', () => testbed.run(() async { - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/index.html'))); - - expect(response.headers, { - 'Content-Type': 'text/html', - 'content-length': '5', - }); - expect(await response.readAsString(), 'hello'); - })); - - test('can serve a sourcemap from dart:ui', () => testbed.run(() async { - final String flutterWebSdkPath = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk); - final File windowSourceFile = globals.fs.file(globals.fs.path.join(flutterWebSdkPath, 'lib', 'ui', 'src', 'ui', 'window.dart')) - ..createSync(recursive: true) - ..writeAsStringSync('test'); - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/packages/build_web_compilers/lib/ui/src/ui/window.dart'))); - - expect(response.headers, { - 'content-length': windowSourceFile.lengthSync().toString(), - }); - expect(await response.readAsString(), 'test'); - })); - - test('can serve a sourcemap from the dart:sdk', () => testbed.run(() async { - final String dartSdkPath = globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath); - final File listSourceFile = globals.fs.file(globals.fs.path.join(dartSdkPath, 'lib', 'core', 'list.dart')) - ..createSync(recursive: true) - ..writeAsStringSync('test'); - - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/packages/dart-sdk/lib/core/list.dart'))); - - expect(response.headers, { - 'content-length': listSourceFile.lengthSync().toString(), - }); - expect(await response.readAsString(), 'test'); - })); - - test('can serve an asset with a png content type', () => testbed.run(() async { - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/assets/foo.png'))); - - expect(response.headers, { - 'Content-Type': 'image/png', - 'content-length': '64', - }); - })); - - test('can fallback to application/octet-stream', () => testbed.run(() async { - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/assets/bar'))); - - expect(response.headers, { - 'Content-Type': 'application/octet-stream', - 'content-length': '3', - }); - })); - - test('handles a missing html file from the web directory', () => testbed.run(() async { - final Response response = await assetServer - .handle(Request('GET', Uri.parse('http://localhost:8080/foobar.html'))); - - expect(response.statusCode, 404); - })); - test('release asset server serves correct mime type and content length for png', () => testbed.run(() async { - assetServer = ReleaseAssetServer(); + final ReleaseAssetServer assetServer = ReleaseAssetServer(); globals.fs.file(globals.fs.path.join('build', 'web', 'assets', 'foo.png')) ..createSync(recursive: true) ..writeAsBytesSync(kTransparentImage); @@ -126,7 +54,7 @@ void main() { })); test('release asset server serves correct mime type and content length for JavaScript', () => testbed.run(() async { - assetServer = ReleaseAssetServer(); + final ReleaseAssetServer assetServer = ReleaseAssetServer(); globals.fs.file(globals.fs.path.join('build', 'web', 'assets', 'foo.js')) ..createSync(recursive: true) ..writeAsStringSync('function main() {}'); @@ -140,7 +68,7 @@ void main() { })); test('release asset server serves correct mime type and content length for html', () => testbed.run(() async { - assetServer = ReleaseAssetServer(); + final ReleaseAssetServer assetServer = ReleaseAssetServer(); globals.fs.file(globals.fs.path.join('build', 'web', 'assets', 'foo.html')) ..createSync(recursive: true) ..writeAsStringSync(''); @@ -154,7 +82,7 @@ void main() { })); test('release asset server serves content from flutter root', () => testbed.run(() async { - assetServer = ReleaseAssetServer(); + final ReleaseAssetServer assetServer = ReleaseAssetServer(); globals.fs.file(globals.fs.path.join('flutter', 'bar.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() { }'); @@ -165,7 +93,7 @@ void main() { })); test('release asset server serves content from project directory', () => testbed.run(() async { - assetServer = ReleaseAssetServer(); + final ReleaseAssetServer assetServer = ReleaseAssetServer(); globals.fs.file(globals.fs.path.join('lib', 'bar.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() { }'); @@ -174,4 +102,4 @@ void main() { expect(response.statusCode, HttpStatus.ok); })); -} +} \ No newline at end of file diff --git a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart b/packages/flutter_tools/test/general.shard/web/web_fs_test.dart deleted file mode 100644 index 6dcd52de031..00000000000 --- a/packages/flutter_tools/test/general.shard/web/web_fs_test.dart +++ /dev/null @@ -1,226 +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:build_daemon/client.dart'; -import 'package:build_daemon/data/build_status.dart'; -import 'package:built_collection/built_collection.dart'; -import 'package:dwds/asset_handler.dart'; -import 'package:dwds/dwds.dart'; -import 'package:flutter_tools/src/base/os.dart'; -import 'package:flutter_tools/src/build_info.dart'; -import 'package:flutter_tools/src/dart/pub.dart'; -import 'package:flutter_tools/src/project.dart'; -import 'package:flutter_tools/src/web/chrome.dart'; -import 'package:flutter_tools/src/build_runner/web_fs.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:http_multi_server/http_multi_server.dart'; -import 'package:meta/meta.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; - -import '../../src/common.dart'; -import '../../src/mocks.dart'; -import '../../src/testbed.dart'; - -void main() { - Testbed testbed; - MockBuildDaemonCreator mockBuildDaemonCreator; - MockDwds mockDwds; - MockChromeLauncher mockChromeLauncher; - MockHttpMultiServer mockHttpMultiServer; - MockBuildDaemonClient mockBuildDaemonClient; - MockOperatingSystemUtils mockOperatingSystemUtils; - MockProcessManager mockProcessManager; - bool lastInitializePlatform; - int lastPort; - - setUp(() { - lastPort = null; - lastInitializePlatform = null; - mockBuildDaemonCreator = MockBuildDaemonCreator(); - mockChromeLauncher = MockChromeLauncher(); - mockHttpMultiServer = MockHttpMultiServer(); - mockBuildDaemonClient = MockBuildDaemonClient(); - mockOperatingSystemUtils = MockOperatingSystemUtils(); - mockDwds = MockDwds(); - mockProcessManager = MockProcessManager(); - when(mockBuildDaemonCreator.startBuildDaemon(any, release: anyNamed('release'), initializePlatform: anyNamed('initializePlatform'))) - .thenAnswer((Invocation invocation) async { - lastInitializePlatform = invocation.namedArguments[#initializePlatform] as bool; - return mockBuildDaemonClient; - }); - when(mockOperatingSystemUtils.findFreePort()).thenAnswer((Invocation _) async { - return 1234; - }); - when(mockProcessManager.start( - any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'), - )).thenAnswer((Invocation invocation) async { - final String workingDirectory = invocation.namedArguments[#workingDirectory] as String; - globals.fs.file(globals.fs.path.join(workingDirectory, '.packages')).createSync(recursive: true); - return FakeProcess(); - }); - when(mockBuildDaemonClient.buildResults).thenAnswer((Invocation _) { - return Stream.fromFuture(Future.value( - BuildResults((BuildResultsBuilder builder) { - builder.results = ListBuilder( - [ - DefaultBuildResult((DefaultBuildResultBuilder builder) { - builder.target = 'web'; - builder.status = BuildStatus.succeeded; - }), - ], - ); - }) - )); - }); - when(mockBuildDaemonCreator.assetServerPort(any)).thenReturn(4321); - testbed = Testbed( - setup: () { - globals.fs.file(globals.fs.path.join('packages', 'flutter_tools', 'pubspec.yaml')) - ..createSync(recursive: true) - ..setLastModifiedSync(DateTime(1991, 08, 23)); - // Create an empty .packages file so we can read it when we check for - // plugins on Webglobals.fs.start() - globals.fs.file('.packages').createSync(); - }, - overrides: { - Pub: () => MockPub(), - OperatingSystemUtils: () => mockOperatingSystemUtils, - BuildDaemonCreator: () => mockBuildDaemonCreator, - ChromeLauncher: () => mockChromeLauncher, - ProcessManager: () => mockProcessManager, - HttpMultiServerFactory: () => (dynamic address, int port) async { - lastPort = port; - return mockHttpMultiServer; - }, - DwdsFactory: () => ({ - @required AssetHandler assetHandler, - @required Stream buildResults, - @required ConnectionProvider chromeConnection, - String hostname, - ReloadConfiguration reloadConfiguration, - bool serveDevTools, - LogWriter logWriter, - bool verbose, - bool enableDebugExtension, - UrlEncoder urlEncoder, - }) async { - return mockDwds; - }, - }, - ); - }); - - test('Can create webFs from mocked interfaces', () => testbed.run(() async { - final FlutterProject flutterProject = FlutterProject.current(); - await WebFs.start( - skipDwds: false, - target: globals.fs.path.join('lib', 'main.dart'), - buildInfo: BuildInfo.debug, - flutterProject: flutterProject, - initializePlatform: true, - hostname: null, - port: null, - urlTunneller: null, - dartDefines: const [], - ); - // Since the .packages file is missing in the memory filesystem, this should - // be called. - verify(pub.get( - context: PubContext.pubGet, - directory: anyNamed('directory'), - offline: true, - skipPubspecYamlCheck: true, - checkLastModified: false, - )).called(1); - - // The build daemon is told to build once. - verify(mockBuildDaemonClient.startBuild()).called(1); - - // .dart_tool directory is created. - expect(flutterProject.dartTool.existsSync(), true); - expect(lastInitializePlatform, true); - })); - - test('Can create webFs from mocked interfaces with initializePlatform', () => testbed.run(() async { - final FlutterProject flutterProject = FlutterProject.current(); - await WebFs.start( - skipDwds: false, - target: globals.fs.path.join('lib', 'main.dart'), - buildInfo: BuildInfo.debug, - flutterProject: flutterProject, - initializePlatform: false, - hostname: null, - port: null, - urlTunneller: null, - dartDefines: const [], - ); - - // The build daemon is told to build once. - verify(mockBuildDaemonClient.startBuild()).called(1); - - // .dart_tool directory is created. - expect(flutterProject.dartTool.existsSync(), true); - expect(lastInitializePlatform, false); - })); - - test('Uses provided port number and hostname.', () => testbed.run(() async { - final FlutterProject flutterProject = FlutterProject.current(); - final WebFs webFs = await WebFs.start( - skipDwds: false, - target: globals.fs.path.join('lib', 'main.dart'), - buildInfo: BuildInfo.debug, - flutterProject: flutterProject, - initializePlatform: false, - hostname: 'localhost', - port: '1234', - urlTunneller: null, - dartDefines: const [], - ); - - expect(webFs.uri.toString(), contains('localhost')); - expect(lastPort, 1234); - })); - - test('Throws exception if build fails', () => testbed.run(() async { - when(mockBuildDaemonClient.buildResults).thenAnswer((Invocation _) { - return Stream.fromFuture(Future.value( - BuildResults((BuildResultsBuilder builder) { - builder.results = ListBuilder( - [ - DefaultBuildResult((DefaultBuildResultBuilder builder) { - builder.target = 'web'; - builder.status = BuildStatus.failed; - }), - ], - ); - }) - )); - }); - final FlutterProject flutterProject = FlutterProject.current(); - - expect(WebFs.start( - skipDwds: false, - target: globals.fs.path.join('lib', 'main.dart'), - buildInfo: BuildInfo.debug, - flutterProject: flutterProject, - initializePlatform: false, - hostname: 'foo', - port: '1234', - urlTunneller: null, - dartDefines: const [], - ), throwsException); - })); -} - -class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {} -class MockBuildDaemonClient extends Mock implements BuildDaemonClient {} -class MockDwds extends Mock implements Dwds {} -class MockHttpMultiServer extends Mock implements HttpMultiServer {} -class MockChromeLauncher extends Mock implements ChromeLauncher {} -class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} -class MockPub extends Mock implements Pub {} -class MockProcessManager extends Mock implements ProcessManager {} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 7953a0025d2..dc6bab5776d 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -710,6 +710,9 @@ class MockResidentCompiler extends BasicMock implements ResidentCompiler { globals.fs.file(outputPath).writeAsStringSync('compiled_kernel_output'); return CompilerOutput(outputPath, 0, []); } + + @override + void addFileSystemRoot(String root) { } } /// A fake implementation of [ProcessResult]. diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index 0f6389c5bfa..be11248526c 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -722,7 +722,6 @@ class TestFeatureFlags implements FeatureFlags { this.isWebEnabled = false, this.isWindowsEnabled = false, this.isAndroidEmbeddingV2Enabled = false, - this.isWebIncrementalCompilerEnabled = false, }); @override @@ -740,9 +739,6 @@ class TestFeatureFlags implements FeatureFlags { @override final bool isAndroidEmbeddingV2Enabled; - @override - final bool isWebIncrementalCompilerEnabled; - @override bool isEnabled(Feature feature) { switch (feature) { @@ -756,8 +752,6 @@ class TestFeatureFlags implements FeatureFlags { return isWindowsEnabled; case flutterAndroidEmbeddingV2Feature: return isAndroidEmbeddingV2Enabled; - case flutterWebIncrementalCompiler: - return isWebIncrementalCompilerEnabled; } return false; }