From d1707ab0adde059eaeea51019609b1048e7fdc1a Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 14 Feb 2019 15:15:01 -0800 Subject: [PATCH 001/395] Revert "Fix overflow clipping/fading for text (#27892)" (#27966) This reverts commit 46cabdab22ce59ba06949d666216ae4d10b76761. --- .../flutter/lib/src/rendering/paragraph.dart | 2 +- packages/flutter/test/widgets/text_test.dart | 69 ------------------- 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 08869df723..1742c190fa 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -287,9 +287,9 @@ class RenderParagraph extends RenderBox { // Other _textPainter state like didExceedMaxLines will also be affected. // See also RenderEditable which has a similar issue. final Size textSize = _textPainter.size; + final bool didOverflowHeight = _textPainter.didExceedMaxLines; size = constraints.constrain(textSize); - final bool didOverflowHeight = size.height < textSize.height || _textPainter.didExceedMaxLines; final bool didOverflowWidth = size.width < textSize.width; // TODO(abarth): We're only measuring the sizes of the line boxes here. If // the glyphs draw outside the line boxes, we might think that there isn't diff --git a/packages/flutter/test/widgets/text_test.dart b/packages/flutter/test/widgets/text_test.dart index f8f374f170..550a70f9a1 100644 --- a/packages/flutter/test/widgets/text_test.dart +++ b/packages/flutter/test/widgets/text_test.dart @@ -7,7 +7,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/foundation.dart'; -import '../rendering/mock_canvas.dart'; import 'semantics_tester.dart'; void main() { @@ -247,72 +246,4 @@ void main() { expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true)); semantics.dispose(); }, skip: true); // TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved. - - testWidgets('Overflow is clipping correctly - short text with overflow: clip', (WidgetTester tester) async { - await _pumpTextWidget( - tester: tester, - overflow: TextOverflow.clip, - text: 'Hi', - ); - - expect(find.byType(Text), isNot(paints..clipRect())); - }); - - testWidgets('Overflow is clipping correctly - long text with overflow: ellipsis', (WidgetTester tester) async { - await _pumpTextWidget( - tester: tester, - overflow: TextOverflow.ellipsis, - text: 'a long long long long text, should be clip', - ); - - expect(find.byType(Text), paints..clipRect(rect: Rect.fromLTWH(0, 0, 50, 50))); - }); - - testWidgets('Overflow is clipping correctly - short text with overflow: ellipsis', (WidgetTester tester) async { - await _pumpTextWidget( - tester: tester, - overflow: TextOverflow.ellipsis, - text: 'Hi', - ); - - expect(find.byType(Text), isNot(paints..clipRect())); - }); - - testWidgets('Overflow is clipping correctly - long text with overflow: fade', (WidgetTester tester) async { - await _pumpTextWidget( - tester: tester, - overflow: TextOverflow.fade, - text: 'a long long long long text, should be clip', - ); - - expect(find.byType(Text), paints..clipRect(rect: Rect.fromLTWH(0, 0, 50, 50))); - }); - - testWidgets('Overflow is clipping correctly - short text with overflow: fade', (WidgetTester tester) async { - await _pumpTextWidget( - tester: tester, - overflow: TextOverflow.fade, - text: 'Hi', - ); - - expect(find.byType(Text), isNot(paints..clipRect())); - }); -} - -Future _pumpTextWidget({ WidgetTester tester, String text, TextOverflow overflow }) { - return tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: Container( - width: 50.0, - height: 50.0, - child: Text( - text, - overflow: overflow, - ), - ), - ), - ), - ); } From d1371cd269ce4a73d79abea12b9e9c1a2d9ae38a Mon Sep 17 00:00:00 2001 From: Anthony Date: Thu, 14 Feb 2019 19:42:30 -0500 Subject: [PATCH 002/395] Do not draw Slider tick marks if they are too dense (#27969) --- packages/flutter/lib/src/material/slider.dart | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index c3f59516e1..9408bbac6a 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -1005,23 +1005,27 @@ class _RenderSlider extends RenderBox { isEnabled: isInteractive, sliderTheme: _sliderTheme, ).width; - for (int i = 0; i <= divisions; i++) { - final double tickValue = i / divisions; - // The ticks are mapped to be within the track, so the tick mark width - // must be subtracted from the track width. - final double tickX = trackRect.left + tickValue * (trackRect.width - tickMarkWidth) + tickMarkWidth / 2; - final double tickY = trackRect.center.dy; - final Offset tickMarkOffset = Offset(tickX, tickY); - _sliderTheme.tickMarkShape.paint( - context, - tickMarkOffset, - parentBox: this, - sliderTheme: _sliderTheme, - enableAnimation: _enableAnimation, - textDirection: _textDirection, - thumbCenter: thumbCenter, - isEnabled: isInteractive, - ); + // If the ticks would be too dense, don't bother painting them. + if ((trackRect.width - tickMarkWidth) / divisions >= 3.0 * tickMarkWidth) { + for (int i = 0; i <= divisions; i++) { + final double tickValue = i / divisions; + // The ticks are mapped to be within the track, so the tick mark width + // must be subtracted from the track width. + final double tickX = trackRect.left + + tickValue * (trackRect.width - tickMarkWidth) + tickMarkWidth / 2; + final double tickY = trackRect.center.dy; + final Offset tickMarkOffset = Offset(tickX, tickY); + _sliderTheme.tickMarkShape.paint( + context, + tickMarkOffset, + parentBox: this, + sliderTheme: _sliderTheme, + enableAnimation: _enableAnimation, + textDirection: _textDirection, + thumbCenter: thumbCenter, + isEnabled: isInteractive, + ); + } } } From 22f888090970c0c99df87d57b8177685112e2c53 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 14 Feb 2019 16:50:06 -0800 Subject: [PATCH 003/395] Make sure the selection is still painted under the text (#27970) --- .../flutter/lib/src/rendering/editable.dart | 25 ++++-- .../flutter/test/rendering/editable_test.dart | 85 +++++++++++++++++++ 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index e2ae7f5346..c7633ab863 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1544,19 +1544,28 @@ class RenderEditable extends RenderBox { assert(_textLayoutLastWidth == constraints.maxWidth); final Offset effectiveOffset = offset + _paintOffset; + bool showSelection = false; + bool showCaret = false; + + if (_selection != null && !_floatingCursorOn) { + if (_selection.isCollapsed && _showCursor.value && cursorColor != null) + showCaret = true; + else if (!_selection.isCollapsed && _selectionColor != null) + showSelection = true; + } + + if (showSelection) { + _selectionRects ??= _textPainter.getBoxesForSelection(_selection); + _paintSelection(context.canvas, effectiveOffset); + } + // On iOS, the cursor is painted over the text, on Android, it's painted // under it. if (paintCursorAboveText) _textPainter.paint(context.canvas, effectiveOffset); - if (_selection != null && !_floatingCursorOn) { - if (_selection.isCollapsed && _showCursor.value && cursorColor != null) { - _paintCaret(context.canvas, effectiveOffset, _selection.extent); - } else if (!_selection.isCollapsed && _selectionColor != null) { - _selectionRects ??= _textPainter.getBoxesForSelection(_selection); - _paintSelection(context.canvas, effectiveOffset); - } - } + if (showCaret) + _paintCaret(context.canvas, effectiveOffset, _selection.extent); if (!paintCursorAboveText) _textPainter.paint(context.canvas, effectiveOffset); diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index ebb433a99b..d637174bbb 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -168,4 +168,89 @@ void main() { expect(editable, paintsExactlyCountTimes(#drawRRect, 0)); }); + + test('text is painted above selection', () { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00), + offset: ViewportOffset.zero(), + textSelectionDelegate: delegate, + text: const TextSpan( + text: 'test', + style: TextStyle( + height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', + ), + ), + selection: const TextSelection( + baseOffset: 0, + extentOffset: 3, + affinity: TextAffinity.upstream, + ), + ); + + layout(editable); + + expect( + editable, + paints + // This is a big selection rectangle, not the cursor. + ..rect(color: Colors.black, rect: Rect.fromLTWH(0, 0, 30, 10)) + ..paragraph(), + ); + + // There is exactly one rect paint (1 selection, 0 cursor). + expect(editable, paintsExactlyCountTimes(#drawRect, 1)); + }); + + test('cursor can paint above or below the text', () { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final ValueNotifier showCursor = ValueNotifier(true); + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + paintCursorAboveText: true, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + showCursor: showCursor, + offset: ViewportOffset.zero(), + textSelectionDelegate: delegate, + text: const TextSpan( + text: 'test', + style: TextStyle( + height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', + ), + ), + selection: const TextSelection.collapsed( + offset: 2, + affinity: TextAffinity.upstream, + ), + ); + + layout(editable); + + expect( + editable, + paints + ..paragraph() + ..rect(color: Colors.red[500], rect: Rect.fromLTWH(20, 2, 1, 6)), + ); + + // There is exactly one rect paint (0 selection, 1 cursor). + expect(editable, paintsExactlyCountTimes(#drawRect, 1)); + + editable.paintCursorAboveText = false; + pumpFrame(); + + expect( + editable, + // The paint order is now flipped. + paints + ..rect(color: Colors.red[500], rect: Rect.fromLTWH(20, 2, 1, 6)) + ..paragraph(), + ); + expect(editable, paintsExactlyCountTimes(#drawRect, 1)); + }); } From 8661d8aecd626f7f57ccbcb735553edc05a2e713 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 14 Feb 2019 19:19:53 -0800 Subject: [PATCH 004/395] Test text paint orders by color (#27983) --- packages/flutter/test/rendering/editable_test.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index d637174bbb..6840f8e4a4 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -175,7 +175,7 @@ void main() { backgroundCursorColor: Colors.grey, selectionColor: Colors.black, textDirection: TextDirection.ltr, - cursorColor: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00), + cursorColor: Colors.red, offset: ViewportOffset.zero(), textSelectionDelegate: delegate, text: const TextSpan( @@ -196,8 +196,8 @@ void main() { expect( editable, paints - // This is a big selection rectangle, not the cursor. - ..rect(color: Colors.black, rect: Rect.fromLTWH(0, 0, 30, 10)) + // Check that it's the black selection box, not the red cursor. + ..rect(color: Colors.black) ..paragraph(), ); @@ -235,7 +235,8 @@ void main() { editable, paints ..paragraph() - ..rect(color: Colors.red[500], rect: Rect.fromLTWH(20, 2, 1, 6)), + // Red collapsed cursor is painted, not a selection box. + ..rect(color: Colors.red[500]), ); // There is exactly one rect paint (0 selection, 1 cursor). @@ -248,7 +249,7 @@ void main() { editable, // The paint order is now flipped. paints - ..rect(color: Colors.red[500], rect: Rect.fromLTWH(20, 2, 1, 6)) + ..rect(color: Colors.red[500]) ..paragraph(), ); expect(editable, paintsExactlyCountTimes(#drawRect, 1)); From 9bc5656637f40ae53db0c70add79da6900ed7d06 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 14 Feb 2019 22:42:30 -0800 Subject: [PATCH 005/395] Wire dart2js through flutter tool, add compilation test (#27668) --- dev/bots/test.dart | 12 ++++ dev/integration_tests/web/lib/main.dart | 13 ++++ dev/integration_tests/web/pubspec.yaml | 17 +++++ .../lib/src/foundation/basic_types.dart | 64 +---------------- .../flutter/lib/src/foundation/bitfield.dart | 67 ++++++++++++++++++ .../src/foundation/bitfield_unsupported.dart | 34 +++++++++ .../lib/src/foundation/unsupported.dart | 7 ++ .../lib/src/foundation/unsupported_web.dart | 7 ++ .../lib/src/application_package.dart | 2 + packages/flutter_tools/lib/src/artifacts.dart | 18 ++++- .../flutter_tools/lib/src/build_info.dart | 10 +++ .../flutter_tools/lib/src/commands/build.dart | 2 + .../lib/src/commands/build_web.dart | 38 ++++++++++ .../flutter_tools/lib/src/context_runner.dart | 2 + .../flutter_tools/lib/src/web/compile.dart | 70 +++++++++++++++++++ .../flutter_tools/test/web/compile_test.dart | 54 ++++++++++++++ 16 files changed, 353 insertions(+), 64 deletions(-) create mode 100644 dev/integration_tests/web/lib/main.dart create mode 100644 dev/integration_tests/web/pubspec.yaml create mode 100644 packages/flutter/lib/src/foundation/bitfield.dart create mode 100644 packages/flutter/lib/src/foundation/bitfield_unsupported.dart create mode 100644 packages/flutter/lib/src/foundation/unsupported.dart create mode 100644 packages/flutter/lib/src/foundation/unsupported_web.dart create mode 100644 packages/flutter_tools/lib/src/commands/build_web.dart create mode 100644 packages/flutter_tools/lib/src/web/compile.dart create mode 100644 packages/flutter_tools/test/web/compile_test.dart diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 41dd11ebe9..947d0d02b7 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -167,10 +167,22 @@ Future _runBuildTests() async { await _flutterBuildApk(path); await _flutterBuildIpa(path); } + await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web')); print('${bold}DONE: All build tests successful.$reset'); } +Future _flutterBuildDart2js(String relativePathToApplication) async { + print('Running Dart2JS build tests...'); + await runCommand(flutter, + ['build', 'web', '-v'], + workingDirectory: path.join(flutterRoot, relativePathToApplication), + expectNonZeroExit: false, + timeout: _kShortTimeout, + ); + print('Done.'); +} + Future _flutterBuildAot(String relativePathToApplication) async { print('Running AOT build tests...'); await runCommand(flutter, diff --git a/dev/integration_tests/web/lib/main.dart b/dev/integration_tests/web/lib/main.dart new file mode 100644 index 0000000000..49c544cb7c --- /dev/null +++ b/dev/integration_tests/web/lib/main.dart @@ -0,0 +1,13 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +void main() { + runApp(Center( + // Can remove when https://github.com/dart-lang/sdk/issues/35801 is fixed. + // ignore: prefer_const_constructors + child: Text('Hello, World', textDirection: TextDirection.ltr), + )); +} diff --git a/dev/integration_tests/web/pubspec.yaml b/dev/integration_tests/web/pubspec.yaml new file mode 100644 index 0000000000..8749907e23 --- /dev/null +++ b/dev/integration_tests/web/pubspec.yaml @@ -0,0 +1,17 @@ +name: web_integration +description: Integration test for web compilation. + +environment: + # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. + sdk: ">=2.0.0-dev.68.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: d53c diff --git a/packages/flutter/lib/src/foundation/basic_types.dart b/packages/flutter/lib/src/foundation/basic_types.dart index 73637cd7da..2c6c875787 100644 --- a/packages/flutter/lib/src/foundation/basic_types.dart +++ b/packages/flutter/lib/src/foundation/basic_types.dart @@ -8,6 +8,7 @@ import 'dart:collection'; // COMMON SIGNATURES export 'dart:ui' show VoidCallback; +export 'bitfield.dart' if (dart.library.html) 'bitfield_unsupported.dart'; /// Signature for callbacks that report that an underlying value has changed. /// @@ -66,69 +67,6 @@ typedef AsyncValueSetter = Future Function(T value); /// * [AsyncValueSetter], the setter equivalent of this signature. typedef AsyncValueGetter = Future Function(); - -// BITFIELD - -/// The largest SMI value. -/// -/// See -const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF; - -/// A BitField over an enum (or other class whose values implement "index"). -/// Only the first 62 values of the enum can be used as indices. -class BitField { - /// Creates a bit field of all zeros. - /// - /// The given length must be at most 62. - BitField(this._length) - : assert(_length <= _smiBits), - _bits = _allZeros; - - /// Creates a bit field filled with a particular value. - /// - /// If the value argument is true, the bits are filled with ones. Otherwise, - /// the bits are filled with zeros. - /// - /// The given length must be at most 62. - BitField.filled(this._length, bool value) - : assert(_length <= _smiBits), - _bits = value ? _allOnes : _allZeros; - - final int _length; - int _bits; - - static const int _smiBits = 62; // see https://www.dartlang.org/articles/numeric-computation/#smis-and-mints - static const int _allZeros = 0; - static const int _allOnes = kMaxUnsignedSMI; // 2^(_kSMIBits+1)-1 - - /// Returns whether the bit with the given index is set to one. - bool operator [](T index) { - assert(index.index < _length); - return (_bits & 1 << index.index) > 0; - } - - /// Sets the bit with the given index to the given value. - /// - /// If value is true, the bit with the given index is set to one. Otherwise, - /// the bit is set to zero. - void operator []=(T index, bool value) { - assert(index.index < _length); - if (value) - _bits = _bits | (1 << index.index); - else - _bits = _bits & ~(1 << index.index); - } - - /// Sets all the bits to the given value. - /// - /// If the value is true, the bits are all set to one. Otherwise, the bits are - /// all set to zero. Defaults to setting all the bits to zero. - void reset([ bool value = false ]) { - _bits = value ? _allOnes : _allZeros; - } -} - - // LAZY CACHING ITERATOR /// A lazy caching version of [Iterable]. diff --git a/packages/flutter/lib/src/foundation/bitfield.dart b/packages/flutter/lib/src/foundation/bitfield.dart new file mode 100644 index 0000000000..5240bc0c13 --- /dev/null +++ b/packages/flutter/lib/src/foundation/bitfield.dart @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The largest SMI value. +/// +/// See +/// +/// When compiling to JavaScript, this value is not supported since it is +/// larger than the maximum safe 32bit integer. +const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF; + +/// A BitField over an enum (or other class whose values implement "index"). +/// Only the first 62 values of the enum can be used as indices. +/// +/// When compiling to JavaScript, this class is not supported. +class BitField { + /// Creates a bit field of all zeros. + /// + /// The given length must be at most 62. + BitField(this._length) + : assert(_length <= _smiBits), + _bits = _allZeros; + + /// Creates a bit field filled with a particular value. + /// + /// If the value argument is true, the bits are filled with ones. Otherwise, + /// the bits are filled with zeros. + /// + /// The given length must be at most 62. + BitField.filled(this._length, bool value) + : assert(_length <= _smiBits), + _bits = value ? _allOnes : _allZeros; + + final int _length; + int _bits; + + static const int _smiBits = 62; // see https://www.dartlang.org/articles/numeric-computation/#smis-and-mints + static const int _allZeros = 0; + static const int _allOnes = kMaxUnsignedSMI; // 2^(_kSMIBits+1)-1 + + /// Returns whether the bit with the given index is set to one. + bool operator [](T index) { + assert(index.index < _length); + return (_bits & 1 << index.index) > 0; + } + + /// Sets the bit with the given index to the given value. + /// + /// If value is true, the bit with the given index is set to one. Otherwise, + /// the bit is set to zero. + void operator []=(T index, bool value) { + assert(index.index < _length); + if (value) + _bits = _bits | (1 << index.index); + else + _bits = _bits & ~(1 << index.index); + } + + /// Sets all the bits to the given value. + /// + /// If the value is true, the bits are all set to one. Otherwise, the bits are + /// all set to zero. Defaults to setting all the bits to zero. + void reset([ bool value = false ]) { + _bits = value ? _allOnes : _allZeros; + } +} \ No newline at end of file diff --git a/packages/flutter/lib/src/foundation/bitfield_unsupported.dart b/packages/flutter/lib/src/foundation/bitfield_unsupported.dart new file mode 100644 index 0000000000..c7baf470b2 --- /dev/null +++ b/packages/flutter/lib/src/foundation/bitfield_unsupported.dart @@ -0,0 +1,34 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Unsupported. +const int kMaxUnsignedSMI = 0; + +/// Unsupported. +class BitField { + /// Unsupported. + // Ignored so that both bitfield implementations have the same API. + // ignore: avoid_unused_constructor_parameters + BitField(int length); + + /// Unsupported. + // Ignored so that both bitfield implementations have the same API. + // ignore: avoid_unused_constructor_parameters + BitField.filled(int length, bool value); + + /// Unsupported. + bool operator [](T index) { + throw UnsupportedError('Not supported when compiling to JavaScript'); + } + + /// Unsupported. + void operator []=(T index, bool value) { + throw UnsupportedError('Not supported when compiling to JavaScript'); + } + + /// Unsupported. + void reset([ bool value = false ]) { + throw UnsupportedError('Not supported when compiling to JavaScript'); + } +} diff --git a/packages/flutter/lib/src/foundation/unsupported.dart b/packages/flutter/lib/src/foundation/unsupported.dart new file mode 100644 index 0000000000..7ad4207f1e --- /dev/null +++ b/packages/flutter/lib/src/foundation/unsupported.dart @@ -0,0 +1,7 @@ +/// The largest SMI value. +/// +/// See +/// +/// When compiling to JavaScript, this value is not supported since it is +/// larger than the maximum safe 32bit integer. +const int kMaxUnsignedSMI = 0x3FFFFFFFFFFFFFFF; \ No newline at end of file diff --git a/packages/flutter/lib/src/foundation/unsupported_web.dart b/packages/flutter/lib/src/foundation/unsupported_web.dart new file mode 100644 index 0000000000..8a23100484 --- /dev/null +++ b/packages/flutter/lib/src/foundation/unsupported_web.dart @@ -0,0 +1,7 @@ +/// The largest SMI value. +/// +/// See +/// +/// When compiling to JavaScript, this value is not supported since it is +/// larger than the maximum safe 32bit integer. +const int kMaxUnsignedSMI = 0; \ No newline at end of file diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index fe8f9687a1..989ca6fb76 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -49,6 +49,7 @@ class ApplicationPackageFactory { case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: + case TargetPlatform.web: return null; } assert(platform != null); @@ -352,6 +353,7 @@ class ApplicationPackageStore { case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: case TargetPlatform.tester: + case TargetPlatform.web: return null; } return null; diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 6cfb8bcd9a..9bf928009b 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -26,6 +26,8 @@ enum Artifact { frontendServerSnapshotForEngineDartSdk, engineDartSdkPath, engineDartBinary, + dart2jsSnapshot, + kernelWorkerSnapshot, } String _artifactToFileName(Artifact artifact, [TargetPlatform platform, BuildMode mode]) { @@ -70,6 +72,10 @@ String _artifactToFileName(Artifact artifact, [TargetPlatform platform, BuildMod return 'frontend_server.dart.snapshot'; case Artifact.engineDartBinary: return 'dart'; + case Artifact.dart2jsSnapshot: + return 'flutter_dart2js.dart.snapshot'; + case Artifact.kernelWorkerSnapshot: + return 'flutter_kernel_worker.dart.snapshot'; } assert(false, 'Invalid artifact $artifact.'); return null; @@ -121,6 +127,7 @@ class CachedArtifacts extends Artifacts { case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: case TargetPlatform.tester: + case TargetPlatform.web: return _getHostArtifactPath(artifact, platform, mode); } assert(false, 'Invalid platform $platform.'); @@ -183,13 +190,17 @@ class CachedArtifacts extends Artifacts { case Artifact.engineDartSdkPath: return dartSdkPath; case Artifact.engineDartBinary: - return fs.path.join(dartSdkPath,'bin', _artifactToFileName(artifact)); + return fs.path.join(dartSdkPath, 'bin', _artifactToFileName(artifact)); case Artifact.platformKernelDill: return fs.path.join(_getFlutterPatchedSdkPath(), _artifactToFileName(artifact)); case Artifact.platformLibrariesJson: return fs.path.join(_getFlutterPatchedSdkPath(), 'lib', _artifactToFileName(artifact)); case Artifact.flutterPatchedSdkPath: return _getFlutterPatchedSdkPath(); + case Artifact.dart2jsSnapshot: + return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact)); + case Artifact.kernelWorkerSnapshot: + return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact)); default: assert(false, 'Artifact $artifact not available for platform $platform.'); return null; @@ -205,6 +216,7 @@ class CachedArtifacts extends Artifacts { case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: case TargetPlatform.tester: + case TargetPlatform.web: assert(mode == null, 'Platform $platform does not support different build modes.'); return fs.path.join(engineDir, platformName); case TargetPlatform.ios: @@ -265,6 +277,10 @@ class LocalEngineArtifacts extends Artifacts { return fs.path.join(_hostEngineOutPath, 'dart-sdk'); case Artifact.engineDartBinary: return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', _artifactToFileName(artifact)); + case Artifact.dart2jsSnapshot: + return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact)); + case Artifact.kernelWorkerSnapshot: + return fs.path.join(_hostEngineOutPath, 'dart-sdk', 'bin', 'snapshots', _artifactToFileName(artifact)); } assert(false, 'Invalid artifact $artifact.'); return null; diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index cd8ce05a77..2e3322b9a5 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -266,6 +266,7 @@ enum TargetPlatform { windows_x64, fuchsia, tester, + web, } /// iOS target device architecture. @@ -325,6 +326,8 @@ String getNameForTargetPlatform(TargetPlatform platform) { return 'fuchsia'; case TargetPlatform.tester: return 'flutter-tester'; + case TargetPlatform.web: + return 'web'; } assert(false); return null; @@ -346,6 +349,8 @@ TargetPlatform getTargetPlatformForName(String platform) { return TargetPlatform.darwin_x64; case 'linux-x64': return TargetPlatform.linux_x64; + case 'web': + return TargetPlatform.web; } assert(platform != null); return null; @@ -400,6 +405,11 @@ String getIosBuildDirectory() { return fs.path.join(getBuildDirectory(), 'ios'); } +/// Returns the web build output directory. +String getWebBuildDirectory() { + return fs.path.join(getBuildDirectory(), 'web'); +} + /// Returns directory used by incremental compiler (IKG - incremental kernel /// generator) to store cached intermediate state. String getIncrementalCompilerByteStoreDirectory() { diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index c31f741718..64100e2be4 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -11,6 +11,7 @@ import 'build_appbundle.dart'; import 'build_bundle.dart'; import 'build_flx.dart'; import 'build_ios.dart'; +import 'build_web.dart'; class BuildCommand extends FlutterCommand { BuildCommand({bool verboseHelp = false}) { @@ -20,6 +21,7 @@ class BuildCommand extends FlutterCommand { addSubcommand(BuildIOSCommand()); addSubcommand(BuildFlxCommand()); addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp)); + addSubcommand(BuildWebCommand()); } @override diff --git a/packages/flutter_tools/lib/src/commands/build_web.dart b/packages/flutter_tools/lib/src/commands/build_web.dart new file mode 100644 index 0000000000..a1a464585e --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_web.dart @@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium 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 '../base/logger.dart'; +import '../build_info.dart'; +import '../globals.dart'; +import '../runner/flutter_command.dart' show ExitStatus, FlutterCommandResult; +import '../web/compile.dart'; +import 'build.dart'; + +class BuildWebCommand extends BuildSubCommand { + BuildWebCommand() { + usesTargetOption(); + usesPubOption(); + defaultBuildMode = BuildMode.release; + } + + @override + final String name = 'web'; + + @override + bool get hidden => true; + + @override + final String description = '(EXPERIMENTAL) build a web application bundle.'; + + @override + Future runCommand() async { + final String target = argResults['target']; + final Status status = logger.startProgress('Compiling $target to JavaScript...', timeout: null); + final int result = await webCompiler.compile(target: target); + status.stop(); + return FlutterCommandResult(result == 0 ? ExitStatus.success : ExitStatus.fail); + } +} diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index ba6a2b1db1..7a4490baf1 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -39,6 +39,7 @@ import 'macos/macos_workflow.dart'; import 'run_hot.dart'; import 'usage.dart'; import 'version.dart'; +import 'web/compile.dart'; import 'windows/windows_workflow.dart'; Future runInContext( @@ -91,6 +92,7 @@ Future runInContext( Usage: () => Usage(), UserMessages: () => UserMessages(), WindowsWorkflow: () => const WindowsWorkflow(), + WebCompiler: () => const WebCompiler(), Xcode: () => Xcode(), XcodeProjectInterpreter: () => XcodeProjectInterpreter(), }, diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart new file mode 100644 index 0000000000..4dab7488f8 --- /dev/null +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -0,0 +1,70 @@ +// Copyright 2019 The Chromium 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:meta/meta.dart'; + +import '../artifacts.dart'; +import '../base/common.dart'; +import '../base/context.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../convert.dart'; +import '../globals.dart'; + +/// The [WebCompiler] instance. +WebCompiler get webCompiler => context[WebCompiler]; + +/// A wrapper around dart2js for web compilation. +class WebCompiler { + const WebCompiler(); + + /// Compile `target` using dart2js. + /// + /// `minify` controls whether minifaction of the source is enabled. Defaults to `true`. + /// `enabledAssertions` controls whether assertions are enabled. Defaults to `false`. + Future compile({@required String target, bool minify = true, bool enabledAssertions = false}) async { + final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary); + final String dart2jsPath = artifacts.getArtifactPath(Artifact.dart2jsSnapshot); + final String flutterPatchedSdkPath = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath); + final String librariesPath = fs.path.join(flutterPatchedSdkPath, 'libraries.json'); + final Directory outputDir = fs.directory(getWebBuildDirectory()); + if (!outputDir.existsSync()) { + outputDir.createSync(recursive: true); + } + final String outputPath = fs.path.join(outputDir.path, 'main.dart.js'); + if (!processManager.canRun(engineDartPath)) { + throwToolExit('Unable to find Dart binary at $engineDartPath'); + } + final List command = [ + engineDartPath, + dart2jsPath, + target, + '-o', + '$outputPath', + '--libraries-spec=$librariesPath', + '--platform-binaries=$flutterPatchedSdkPath', + ]; + if (minify) { + command.add('-m'); + } + if (enabledAssertions) { + command.add('--enable-asserts'); + } + printTrace(command.join(' ')); + final Process result = await processManager.start(command); + result + .stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printStatus); + result + .stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printError); + return result.exitCode; + } +} diff --git a/packages/flutter_tools/test/web/compile_test.dart b/packages/flutter_tools/test/web/compile_test.dart new file mode 100644 index 0000000000..0316a33a5b --- /dev/null +++ b/packages/flutter_tools/test/web/compile_test.dart @@ -0,0 +1,54 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/globals.dart'; +import 'package:flutter_tools/src/web/compile.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; + +import '../src/context.dart'; + +void main() { + final MockProcessManager mockProcessManager = MockProcessManager(); + final MockProcess mockProcess = MockProcess(); + final BufferLogger mockLogger = BufferLogger(); + + testUsingContext('invokes dart2js with correct arguments', () async { + const WebCompiler webCompiler = WebCompiler(); + final String engineDartPath = artifacts.getArtifactPath(Artifact.engineDartBinary); + final String dart2jsPath = artifacts.getArtifactPath(Artifact.dart2jsSnapshot); + final String flutterPatchedSdkPath = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath); + final String librariesPath = fs.path.join(flutterPatchedSdkPath, 'libraries.json'); + + when(mockProcess.stdout).thenAnswer((Invocation invocation) => const Stream>.empty()); + when(mockProcess.stderr).thenAnswer((Invocation invocation) => const Stream>.empty()); + when(mockProcess.exitCode).thenAnswer((Invocation invocation) async => 0); + when(mockProcessManager.start(any)).thenAnswer((Invocation invocation) async => mockProcess); + when(mockProcessManager.canRun(engineDartPath)).thenReturn(true); + + await webCompiler.compile(target: 'lib/main.dart'); + + final String outputPath = fs.path.join('build', 'web', 'main.dart.js'); + verify(mockProcessManager.start([ + engineDartPath, + dart2jsPath, + 'lib/main.dart', + '-o', + outputPath, + '--libraries-spec=$librariesPath', + '--platform-binaries=$flutterPatchedSdkPath', + '-m', + ])).called(1); + }, overrides: { + ProcessManager: () => mockProcessManager, + Logger: () => mockLogger, + }); +} + +class MockProcessManager extends Mock implements ProcessManager {} +class MockProcess extends Mock implements Process {} \ No newline at end of file From 3205736fcf09f52e6d5e97916f5eb314e6849c12 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 14 Feb 2019 22:49:49 -0800 Subject: [PATCH 006/395] add ui.Window fallback to TestViewConfiguration (#27987) --- packages/flutter_test/lib/src/binding.dart | 10 ++++++++-- packages/flutter_test/test/bindings_test.dart | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 packages/flutter_test/test/bindings_test.dart diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 35595eb5af..a7261b9153 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -1319,10 +1319,16 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { /// size onto the actual display using the [BoxFit.contain] algorithm. class TestViewConfiguration extends ViewConfiguration { /// Creates a [TestViewConfiguration] with the given size. Defaults to 800x600. - TestViewConfiguration({ + /// + /// If a [window] instance is not provided it defaults to [ui.window]. + factory TestViewConfiguration({ Size size = _kDefaultTestViewportSize, ui.Window window, - }) + }) { + return TestViewConfiguration._(size, window ?? ui.window); + } + + TestViewConfiguration._(Size size, ui.Window window) : _paintMatrix = _getMatrix(size, window.devicePixelRatio, window), _hitTestMatrix = _getMatrix(size, 1.0, window), super(size: size); diff --git a/packages/flutter_test/test/bindings_test.dart b/packages/flutter_test/test/bindings_test.dart new file mode 100644 index 0000000000..eef7e41560 --- /dev/null +++ b/packages/flutter_test/test/bindings_test.dart @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + + +void main() { + group(TestViewConfiguration, () { + test('is initialized with top-level window if one is not provided', () { + // The code below will throw without the default. + TestViewConfiguration(size: const Size(1280.0, 800.0)); + }); + }); +} From 67cf21577f40ee6dd87628316c16ebdc72eb8498 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 14 Feb 2019 23:17:16 -0800 Subject: [PATCH 007/395] Add basic codegen app to be used for integration testing and benchmarks (#27257) --- .../bin/tasks/codegen_integration_test.dart | 20 ++ .../lib/tasks/integration_tests.dart | 14 +- dev/devicelab/manifest.yaml | 8 + dev/integration_tests/codegen/README.md | 3 + .../codegen/android/.project | 17 + .../org.eclipse.buildship.core.prefs | 2 + .../codegen/android/app/build.gradle | 57 ++++ .../android/app/src/main/AndroidManifest.xml | 28 ++ .../platforminteraction/MainActivity.java | 17 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../codegen/android/build.gradle | 29 ++ .../codegen/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../codegen/android/settings.gradle | 15 + .../codegen/lib/coffee_app.dart | 58 ++++ dev/integration_tests/codegen/lib/main.dart | 49 +++ .../codegen/lib/src/coffee.dart | 41 +++ dev/integration_tests/codegen/pubspec.yaml | 88 ++++++ .../codegen/test_driver/main_test.dart | 23 ++ .../flutter_build/lib/src/kernel_builder.dart | 32 +- packages/flutter_build/pubspec.yaml | 4 +- packages/flutter_tools/BUILD.gn | 1 + packages/flutter_tools/lib/executable.dart | 15 +- .../flutter_tools/lib/src/base/build.dart | 1 - .../build_runner/build_kernel_compiler.dart | 64 ---- .../lib/src/build_runner/build_runner.dart | 154 ++++++--- .../build_runner/build_script_generator.dart | 13 +- packages/flutter_tools/lib/src/bundle.dart | 1 - packages/flutter_tools/lib/src/codegen.dart | 293 ++++++++++++++++++ .../lib/src/commands/generate.dart | 30 ++ .../flutter_tools/lib/src/commands/run.dart | 7 + .../lib/src/commands/update_packages.dart | 9 +- packages/flutter_tools/lib/src/compile.dart | 13 +- .../flutter_tools/lib/src/context_runner.dart | 3 +- packages/flutter_tools/lib/src/project.dart | 17 +- .../lib/src/resident_runner.dart | 6 + packages/flutter_tools/lib/src/run_hot.dart | 9 +- packages/flutter_tools/pubspec.yaml | 14 +- .../test/build_runner/build_runner_test.dart | 2 +- ...t => code_generating_kernel_compiler.dart} | 13 +- packages/flutter_tools/test/compile_test.dart | 3 - .../test/forbidden_imports_test.dart | 28 ++ .../test/tester/flutter_tester_test.dart | 8 +- 47 files changed, 1027 insertions(+), 189 deletions(-) create mode 100644 dev/devicelab/bin/tasks/codegen_integration_test.dart create mode 100644 dev/integration_tests/codegen/README.md create mode 100644 dev/integration_tests/codegen/android/.project create mode 100644 dev/integration_tests/codegen/android/.settings/org.eclipse.buildship.core.prefs create mode 100644 dev/integration_tests/codegen/android/app/build.gradle create mode 100644 dev/integration_tests/codegen/android/app/src/main/AndroidManifest.xml create mode 100644 dev/integration_tests/codegen/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java create mode 100644 dev/integration_tests/codegen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 dev/integration_tests/codegen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 dev/integration_tests/codegen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 dev/integration_tests/codegen/android/build.gradle create mode 100644 dev/integration_tests/codegen/android/gradle.properties create mode 100755 dev/integration_tests/codegen/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 dev/integration_tests/codegen/android/settings.gradle create mode 100644 dev/integration_tests/codegen/lib/coffee_app.dart create mode 100644 dev/integration_tests/codegen/lib/main.dart create mode 100644 dev/integration_tests/codegen/lib/src/coffee.dart create mode 100644 dev/integration_tests/codegen/pubspec.yaml create mode 100644 dev/integration_tests/codegen/test_driver/main_test.dart delete mode 100644 packages/flutter_tools/lib/src/build_runner/build_kernel_compiler.dart create mode 100644 packages/flutter_tools/lib/src/codegen.dart create mode 100644 packages/flutter_tools/lib/src/commands/generate.dart rename packages/flutter_tools/test/build_runner/{build_kernel_compiler_test.dart => code_generating_kernel_compiler.dart} (80%) diff --git a/dev/devicelab/bin/tasks/codegen_integration_test.dart b/dev/devicelab/bin/tasks/codegen_integration_test.dart new file mode 100644 index 0000000000..2074b422d4 --- /dev/null +++ b/dev/devicelab/bin/tasks/codegen_integration_test.dart @@ -0,0 +1,20 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +final Directory codegenAppPath = dir(path.join(flutterDirectory.path, 'dev/integration_tests/codegen')); + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createCodegenerationIntegrationTest()); +} diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart index e38f4a2284..334c2a06f1 100644 --- a/dev/devicelab/lib/tasks/integration_tests.dart +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -61,6 +61,16 @@ TaskFunction createAndroidSemanticsIntegrationTest() { ); } +TaskFunction createCodegenerationIntegrationTest() { + return DriverTest( + '${flutterDirectory.path}/dev/integration_tests/codegen', + 'lib/main.dart', + environment: { + 'FLUTTER_EXPERIMENTAL_BUILD': 'true' + }, + ); +} + TaskFunction createFlutterCreateOfflineTest() { return () async { final Directory tempDir = Directory.systemTemp.createTempSync('flutter_create_test.'); @@ -83,12 +93,14 @@ class DriverTest { this.testDirectory, this.testTarget, { this.extraOptions = const [], + this.environment = const {}, } ); final String testDirectory; final String testTarget; final List extraOptions; + final Map environment; Future call() { return inDirectory(testDirectory, () async { @@ -107,7 +119,7 @@ class DriverTest { deviceId, ]; options.addAll(extraOptions); - await flutter('drive', options: options); + await flutter('drive', options: options, environment: Map.from(environment)); return TaskResult.success(null); }); diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 861d0637a9..0d89a03173 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -76,6 +76,14 @@ tasks: stage: devicelab_win required_agent_capabilities: ["windows/android"] + codegen_integration_test: + description: > + Runs codegeneration and verifies that it can execute + correctly. + stage: devicelab_win + required_agent_capabilities: ["windows/android"] + flaky: true + flutter_gallery_android__compile: description: > Collects various performance metrics of compiling the Flutter diff --git a/dev/integration_tests/codegen/README.md b/dev/integration_tests/codegen/README.md new file mode 100644 index 0000000000..494dc2bbcc --- /dev/null +++ b/dev/integration_tests/codegen/README.md @@ -0,0 +1,3 @@ +# codegen + +A Flutter project for testing code generation. diff --git a/dev/integration_tests/codegen/android/.project b/dev/integration_tests/codegen/android/.project new file mode 100644 index 0000000000..8b6e7b429f --- /dev/null +++ b/dev/integration_tests/codegen/android/.project @@ -0,0 +1,17 @@ + + + android_____ + Project android_____ created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/dev/integration_tests/codegen/android/.settings/org.eclipse.buildship.core.prefs b/dev/integration_tests/codegen/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000000..e8895216fd --- /dev/null +++ b/dev/integration_tests/codegen/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/dev/integration_tests/codegen/android/app/build.gradle b/dev/integration_tests/codegen/android/app/build.gradle new file mode 100644 index 0000000000..9ebebef363 --- /dev/null +++ b/dev/integration_tests/codegen/android/app/build.gradle @@ -0,0 +1,57 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { stream -> + localProperties.load(stream) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName "0.0.1" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + + aaptOptions { + // TODO(goderbauer): remove when https://github.com/flutter/flutter/issues/8986 is resolved. + if(System.getenv("FLUTTER_CI_WIN")) { + println "AAPT cruncher disabled when running on Win CI." + cruncherEnabled false + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/dev/integration_tests/codegen/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/codegen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..6703641433 --- /dev/null +++ b/dev/integration_tests/codegen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/codegen/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java b/dev/integration_tests/codegen/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java new file mode 100644 index 0000000000..73b7e51bb9 --- /dev/null +++ b/dev/integration_tests/codegen/android/app/src/main/java/com/yourcompany/platforminteraction/MainActivity.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.yourcompany.platforminteraction; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/dev/integration_tests/codegen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dev/integration_tests/codegen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84rT@hh9qO>QU(SF$r9IylHmNblJdl&R0hYC z{G?O`&)mfH)S%SFl*+=BsWuD@4C?}XLR=Xb7#RJ!7{h04Zu=H;^aq4w44VZJaXaum zG@>0o`I5 z6U6gpN>(hBs@ovlyiK}omrVN}&28UY_JdsVcA=Cl0|SF_NswPKgMfg5KtMnO2sE4r zf%}pIPku2lFue41aSVw#{PxUsz9t6&hrp0GdlTMw^~h@f`fs1s!olsWbZ#C0v7PJ9 zExVjNeb(Ol`RU07XFf|X@qSK7qke0_C&kUH>MnD(8u-mi5B zt(&|xZ@%@sBNk%nt_5+E&U1QAlyT>>sMB2Zd5T5*R3&Sld5=98o%0qdm+@@-JjKB8 z(xcUiGOB?O=e)LCdt!o~wXBiQlFwyn4J{(JaZG%Q-e|yQz{EjrrIztFmwg@gt#&=FffMC4mtWmbKAF&qdyqKXG0ipk&vT5 zAmZ%d{SXF=bCKq@Z-GZZ;tV#~j83JDzFmxd-Hahq8AGNqhE8V;o5>h9i!po_WB6=l zU2kT?Kvt(bR^JA;;7+;DUGlvL6sMk6nsrHO&Q;BA-wby`jSBSdXJueu5G@Jv3uX`y zP*5;1FzBB@fBpXR=g+?ff&5MV!VC-yOFdm2Lp+YZy>e2h$w0vMVh8iNMNLhM)++IJ z{QqCg*LgRGXKpKZaOLSMJFo8MNc{figWv3mbJp+w-BoBm(|-hT|4dcbJ2lEyX!??Y`jpZ81*D{;rF6b`?;0>>pbJRKPRw2)?8wK8GH9> zL)n=k&vkA#ITl(SSsBW`??>AG{pM|AK2Jc#t6>y4N#$)H8ZEGkUc!`m{6pbTImMF#2{f z`gJk-_b~?aGX_m$44TXsJQ-wH$W+GA>5O4B7{g{VhRtFOpT!tHo5?tW$tsn}K99+{ zjLE&4*|Uz>tC88eNi3sZJbQ{n(LBkDd1t=Sn-plsE283-d*czTa-zm@iQD#kujt@|Kvt4hp|H z71QJA-@DE-vCc=}m3vdfeqD}l&W>B;IduNM%M_}RPx;yPVrk2E_T?&?d+vqXsA}xF zGk52_Ny78;-$vOoENNLjTll#WOG?YLuZ?eO;uVxk)@?h*_~6>qo(~l}_f@BaD>p=c zm>yaWRpaQm|`^KGX?!;c=*_;KycpYksb1 rdBk${xlomwC<})`ozHbP0l+XkKcl<5t literal 0 HcmV?d00001 diff --git a/dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H9Lx+13>Rhybuln7NS3%plmzFem6RtIr7}3C z$Dctjh(SM?K|h4SAdJB{hQT_6!9JhSFr3jep3y9U z(L9OKDxJ|flhG!d(I$t{HiywRm(ebd(Y}z;v6#`Zgwd&#(Yb=rrHawDn$fL>(XE!z zt&Y*Xj?trm(W8;kvysuWiP5u}(X)lotC`WOh0&*-(Wisaw}a8QlhLo6(XWTmzlYJk zmocD^F|eO8Z~|lCM8?2Lj6o9_gC>F8A3T{ccnV|4RK}2LjG@yRLuW9C&0q|h$rv_^ zF?<$d_-uBwU=HgjPRC?Uw`?x2A}+rQ?!a2^kVfvX7VhwN?#NE=s4niP9-inPl?69d z7T#1{bVqf`J=LWT)RsR~Tk%+JQT9|ZQFKYxDR#LKT37?|`uT^vIyZoR#b9vmDf()v(?iz7_L)oF78 zkJJ<)r8pUt8M$!5-N#*39u$gn zR>-(G$wg0`aolam=a?PG9$oC(GH>-!X0d*^l*gJvuFJp95x7~EvFLL3EEhhxt`pq; z%_lBC*86lUSiR`uj?O)w{s`}^n&Wz}@?RH+ochG+^SU~oY!t72t=ZWjXFc({Ty9i< zoXX3Kohi2Ju9xPeP296QFr{gETuR&Xh?K77YDUL{MQ1+I?2fS6ciP3jaR24wmqlki zKfgU-wdbO}>o1>vX;$n~9ltDoY4ItEi|?MFbkSNqMRUkMU-}~Bs z)+NdNzgg`VE)XKX=tVT|2z5{L4{YJjHWS*rIm-E6Ws`IFvdRnm87H fR&jd1QhDAN-h`R^uRUjAU|{fc^>bP0l+XkKAv&s= literal 0 HcmV?d00001 diff --git a/dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dev/integration_tests/codegen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFy0FA32|j$U7=vLLgJC#>aTJ4T9D`W`gLwjjbtZ#N4x^eAqnb0LmM5cOIHO54qe%>- zNi3sjJfm3xqeU{KMGB*38lzP@qg4i@bta=t7NczrqirsuT`r?t9;018qkTT3eF39G z5u;-~4uK+6lW`UY0|SF-NswPKgMfko7&thbzyBTz-gcI+Wnf^o^K@|x zskrs_3VX0{ph#Pyi^G})N%|2R%{L?-ZV(U%Y+lqcfu(WAio}U)uIrV(+3WUaecD+A zmQ6SJem|Gdo_Kp z-*x@+`J3Y19B#kQTkx(t_`-K<>p6dn#rh_GuokuWC9d+nXX1zFdIcZVw{qEhc&=6O z^|eUFqwV?=KCBk6c(Pr4!l%_@70=G=P581}wBp75D22D-njfy%?{qk*AOB#Z{bq-g z`q2+|)-Q6ns2@Io{eMN{rubzF`u|EA+wSjG@UL6#aQc1N1oQe;4(H#;OmN?SS>~@F ztIeOC+&g|0i+=ccTfE@YZP5>((!~ot`|Cb zb^PFdrt-VXypJEaJ95?T2$v)Ws{O#V4Ednvz`|Pb} zf4VOe!@bWom-}e`tT2YtP3!%mny=z+}-K6mV2ZEt5Icwo(g z%Zr!wX>UAuh9QhqUj7<;)5IBxDQpH>KRz6|eoM6P*rQ_`EdPlXYgN?$U-3W2|NYf9 z3~H?J-^u1!lq%j3KgpL6_~F~TxcJXA&m~w0?N0Rh)m167XXobE9?sqiHmlnth&$|i z@b&H6->;epMCgPeb()B(Tr|RH~O+1PwlVxdT!ROU2OGQ3PvhLeQAwP zO|AMWqpQ}lM;-?Ix_3Z(ZoZK*%HiZ|EUDeVf^rGH<21r@EM zSf|zZ`p5aNP1ut4@|gFs0GYW>Kcl68^QPJ_Y plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/dev/integration_tests/codegen/lib/coffee_app.dart b/dev/integration_tests/codegen/lib/coffee_app.dart new file mode 100644 index 0000000000..daa5b4225f --- /dev/null +++ b/dev/integration_tests/codegen/lib/coffee_app.dart @@ -0,0 +1,58 @@ +// Copyright 2019 The Chromium 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:inject/inject.dart'; + +// This is a compile-time generated file and does not exist in source. +import 'coffee_app.inject.dart' as generated; // ignore: uri_does_not_exist +import 'src/coffee.dart'; + +@module +class PourOverCoffeeModule { + @provide + @brandName + String provideBrand() => 'Coffee by Flutter Inc.'; + + @provide + @modelName + String provideModel() => 'PourOverSupremeFiesta'; + + @provide + @asynchronous + Future provideHeater() async => Stove(); + + @provide + Pump providePump(Heater heater) => NoOpPump(); +} + +class NoOpPump extends Pump { + @override + void pump() { + print('nothing to pump...'); + } +} + +class Stove extends Heater { + @override + bool get isHot => _isHot; + bool _isHot = false; + + @override + void off() { + _isHot = true; + } + + @override + void on() { + _isHot = true; + } +} + +@Injector([PourOverCoffeeModule]) +abstract class CoffeeApp { + static final Future Function(PourOverCoffeeModule) create = generated.CoffeeApp$Injector.create; + + @provide + CoffeeMaker getCoffeeMaker(); +} diff --git a/dev/integration_tests/codegen/lib/main.dart b/dev/integration_tests/codegen/lib/main.dart new file mode 100644 index 0000000000..b136fba220 --- /dev/null +++ b/dev/integration_tests/codegen/lib/main.dart @@ -0,0 +1,49 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_driver/driver_extension.dart'; + +import 'coffee_app.dart'; +import 'src/coffee.dart'; + +Future main() async { + enableFlutterDriverExtension(); + coffeeApp = await CoffeeApp.create(PourOverCoffeeModule()); + runApp(ExampleWidget()); +} + +CoffeeApp coffeeApp; + +class ExampleWidget extends StatefulWidget { + @override + _ExampleWidgetState createState() => _ExampleWidgetState(); +} + +class _ExampleWidgetState extends State { + String _message = ''; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: const Text('Press Button, Get Coffee'), + onPressed: () async { + final CoffeeMaker coffeeMaker = coffeeApp.getCoffeeMaker(); + setState(() { + _message = coffeeMaker.brew(); + }); + }, + ), + Text(_message), + ], + ), + ), + ); + } +} diff --git a/dev/integration_tests/codegen/lib/src/coffee.dart b/dev/integration_tests/codegen/lib/src/coffee.dart new file mode 100644 index 0000000000..5b33d77ce0 --- /dev/null +++ b/dev/integration_tests/codegen/lib/src/coffee.dart @@ -0,0 +1,41 @@ +// Copyright 2019 The Chromium 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:inject/inject.dart'; + +const Qualifier brandName = Qualifier(#brandName); +const Qualifier modelName = Qualifier(#modelName); + +class CoffeeMaker { + @provide + CoffeeMaker(this._heater, this._pump, this._brand, this._model); + + final Heater _heater; + final Pump _pump; + + @modelName + final String _model; + + @brandName + final String _brand; + + String brew() { + _heater.on(); + _pump.pump(); + print(' [_]P coffee! [_]P'); + final String message = 'Thanks for using $_model by $_brand'; + _heater.off(); + return message; + } +} + +abstract class Heater { + void on(); + void off(); + bool get isHot; +} + +abstract class Pump { + void pump(); +} diff --git a/dev/integration_tests/codegen/pubspec.yaml b/dev/integration_tests/codegen/pubspec.yaml new file mode 100644 index 0000000000..c45fed8a73 --- /dev/null +++ b/dev/integration_tests/codegen/pubspec.yaml @@ -0,0 +1,88 @@ +name: codegen +description: A test of Flutter integrating code generation. + +environment: + # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. + sdk: ">=2.0.0-dev.68.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + # TODO(jonahwilliams): replace with pub version when everything is compatible + inject: + git: + url: https://github.com/jonahwilliams/inject.dart + path: package/inject + + async: 2.0.8 # 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" + 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" + crypto: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 5.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + intl: 0.15.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_rpc_2: 2.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.5.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 1.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + 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" + +dev_dependencies: + test: 1.5.3 + + analyzer: 0.35.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 0.14.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + front_end: 0.1.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 1.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.13.3+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http: 0.12.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 2.0.5 # 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" + io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + kernel: 0.3.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 0.11.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 0.9.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_resolver: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pedantic: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + plugin: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 0.7.4 # 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_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.2.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + utf: 0.9.0+5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 2.1.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +builders: + # TODO(jonahwilliams): replace with pub version when everything is compatible + inject_generator: + git: + url: https://github.com/jonahwilliams/inject.dart + path: package/inject_generator + +flutter: + uses-material-design: true + +# PUBSPEC CHECKSUM: c26c diff --git a/dev/integration_tests/codegen/test_driver/main_test.dart b/dev/integration_tests/codegen/test_driver/main_test.dart new file mode 100644 index 0000000000..33824ab46a --- /dev/null +++ b/dev/integration_tests/codegen/test_driver/main_test.dart @@ -0,0 +1,23 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + test('Can execute generated code', () async { + const String button = 'Press Button, Get Coffee'; + await driver.tap(find.text(button)); + + const String message = 'Thanks for using PourOverSupremeFiesta by Coffee by Flutter Inc.'; + final String fullMessage = await driver.getText(find.text(message)); + expect(fullMessage, message); + }); +} \ No newline at end of file diff --git a/packages/flutter_build/lib/src/kernel_builder.dart b/packages/flutter_build/lib/src/kernel_builder.dart index 65f873dd33..447848817a 100644 --- a/packages/flutter_build/lib/src/kernel_builder.dart +++ b/packages/flutter_build/lib/src/kernel_builder.dart @@ -84,6 +84,11 @@ class FlutterKernelBuilder implements Builder { @override Future build(BuildStep buildStep) async { + // Do not resolve dependencies if this does not correspond to the main + // entrypoint. + if (!mainPath.contains(buildStep.inputId.path)) { + return; + } final AssetId outputId = buildStep.inputId.changeExtension(_kFlutterDillOutputExtension); final AssetId packagesOutputId = buildStep.inputId.changeExtension(_kPackagesExtension); @@ -97,9 +102,8 @@ class FlutterKernelBuilder implements Builder { return; } - // Do not generate kernel if it has been disabled or if this asset does not - // correspond to the current entrypoint. - if (disabled || !mainPath.contains(buildStep.inputId.path)) { + // Do not generate kernel if it has been disabled. + if (disabled) { return; } @@ -118,8 +122,14 @@ class FlutterKernelBuilder implements Builder { // Note: currently we only replace the root package with a multiroot // scheme. To support codegen on arbitrary packages we will need to do // this for each dependency. - final String newPackagesContents = oldPackagesContents.replaceFirst('$packageName:lib/', '$packageName:$multiRootScheme:///lib/'); + final String newPackagesContents = oldPackagesContents.replaceFirst('$packageName:lib/', '$packageName:$multiRootScheme:/'); await packagesFile.writeAsString(newPackagesContents); + String absoluteMainPath; + if (path.isAbsolute(mainPath)) { + absoluteMainPath = mainPath; + } else { + absoluteMainPath = path.join(projectDir.absolute.path, mainPath); + } // start up the frontend server with configuration. final List arguments = [ @@ -145,14 +155,15 @@ class FlutterKernelBuilder implements Builder { if (incrementalCompilerByteStorePath != null) { arguments.add('--incremental'); } - final String generatedRoot = path.join(projectDir.absolute.path, '.dart_tool', 'build', 'generated', '$packageName'); + final String generatedRoot = path.join(projectDir.absolute.path, '.dart_tool', 'build', 'generated', '$packageName', 'lib'); + final String normalRoot = path.join(projectDir.absolute.path, 'lib'); arguments.addAll([ '--packages', packagesFile.path, '--output-dill', outputFile.path, '--filesystem-root', - projectDir.absolute.path, + normalRoot, '--filesystem-root', generatedRoot, '--filesystem-scheme', @@ -162,12 +173,12 @@ class FlutterKernelBuilder implements Builder { arguments.addAll(extraFrontEndOptions); } final Uri mainUri = _PackageUriMapper.findUri( - mainPath, + absoluteMainPath, packagesFile.path, multiRootScheme, - [projectDir.absolute.path, generatedRoot], + [normalRoot, generatedRoot], ); - arguments.add(mainUri.toString()); + arguments.add(mainUri?.toString() ?? absoluteMainPath); // Invoke the frontend server and copy the dill back to the output // directory. try { @@ -202,7 +213,6 @@ class _StdoutHandler { bool _suppressCompilerMessages; void handler(String message) { - log.info(message); const String kResultPrefix = 'result '; if (boundaryKey == null) { if (message.startsWith(kResultPrefix)) @@ -256,7 +266,7 @@ class _PackageUriMapper { if (fileSystemScheme != null && fileSystemRoots != null && prefix.contains(fileSystemScheme)) { _packageName = packageName; _uriPrefixes = fileSystemRoots - .map((String name) => Uri.file('$name/lib/', windows: Platform.isWindows).toString()) + .map((String name) => Uri.file(name, windows: Platform.isWindows).toString()) .toList(); return; } diff --git a/packages/flutter_build/pubspec.yaml b/packages/flutter_build/pubspec.yaml index eb88dadab8..7996be6ebd 100644 --- a/packages/flutter_build/pubspec.yaml +++ b/packages/flutter_build/pubspec.yaml @@ -7,7 +7,7 @@ environment: dependencies: # To update these, use "flutter update-packages --force-upgrade". build: 1.1.1 - build_modules: 1.0.7 + build_modules: 1.0.7+2 package_config: 1.0.5 path: 1.6.2 @@ -50,4 +50,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 0361 +# PUBSPEC CHECKSUM: dcbe diff --git a/packages/flutter_tools/BUILD.gn b/packages/flutter_tools/BUILD.gn index 1838e49ecc..6ba47615f1 100644 --- a/packages/flutter_tools/BUILD.gn +++ b/packages/flutter_tools/BUILD.gn @@ -18,6 +18,7 @@ dart_library("flutter_tools") { "//third_party/dart/third_party/pkg/linter", "//third_party/dart-pkg/pub/archive", "//third_party/dart-pkg/pub/args", + "//third_party/dart-pkg/pub/build_daemon", "//third_party/dart-pkg/pub/build_runner_core", "//third_party/dart-pkg/pub/collection", "//third_party/dart-pkg/pub/completion", diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 160cfc3219..1415fa5f2c 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -5,6 +5,12 @@ import 'dart:async'; import 'runner.dart' as runner; +import 'src/base/context.dart'; +// The build_runner code generation is provided here to make it easier to +// avoid introducing the dependency into google3. Not all build* packages +// are synced internally. +import 'src/build_runner/build_runner.dart'; +import 'src/codegen.dart'; import 'src/commands/analyze.dart'; import 'src/commands/attach.dart'; import 'src/commands/build.dart'; @@ -18,6 +24,7 @@ import 'src/commands/doctor.dart'; import 'src/commands/drive.dart'; import 'src/commands/emulators.dart'; import 'src/commands/format.dart'; +import 'src/commands/generate.dart'; import 'src/commands/ide_config.dart'; import 'src/commands/inject_plugins.dart'; import 'src/commands/install.dart'; @@ -63,6 +70,7 @@ Future main(List args) async { DriveCommand(), EmulatorsCommand(), FormatCommand(), + GenerateCommand(), IdeConfigCommand(hidden: !verboseHelp), InjectPluginsCommand(hidden: !verboseHelp), InstallCommand(), @@ -81,5 +89,10 @@ Future main(List args) async { VersionCommand(), ], verbose: verbose, muteCommandLogging: muteCommandLogging, - verboseHelp: verboseHelp); + verboseHelp: verboseHelp, + overrides: { + // The build runner instance is not supported in google3 because + // the build runner packages are not synced internally. + CodeGenerator: () => experimentalBuildEnabled ? const BuildRunner() : const UnsupportedCodeGenerator(), + }); } diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index a62a353203..7be3b8c203 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -302,7 +302,6 @@ class AOTSnapshotter { printTrace('Extra front-end options: $extraFrontEndOptions'); final String depfilePath = fs.path.join(outputPath, 'kernel_compile.d'); - final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(); final CompilerOutput compilerOutput = await kernelCompiler.compile( sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), mainPath: mainPath, diff --git a/packages/flutter_tools/lib/src/build_runner/build_kernel_compiler.dart b/packages/flutter_tools/lib/src/build_runner/build_kernel_compiler.dart deleted file mode 100644 index d60bc8cba6..0000000000 --- a/packages/flutter_tools/lib/src/build_runner/build_kernel_compiler.dart +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import '../base/file_system.dart'; -import '../compile.dart'; -import '../globals.dart'; -import 'build_runner.dart'; - -/// An implementation of the [KernelCompiler] which delegates to build_runner. -/// -/// Only a subset of the arguments provided to the [KernelCompiler] are -/// supported here. Using the build pipeline implies a fixed multiroot -/// filesystem and requires a pubspec. -/// -/// This is only safe to use if [experimentalBuildEnabled] is true. -class BuildKernelCompiler implements KernelCompiler { - const BuildKernelCompiler(); - - @override - Future compile({ - String mainPath, - String outputFilePath, - bool linkPlatformKernelIn = false, - bool aot = false, - bool trackWidgetCreation, - List extraFrontEndOptions, - String incrementalCompilerByteStorePath, - bool targetProductVm = false, - // These arguments are currently unused. - String sdkRoot, - String packagesPath, - List fileSystemRoots, - String fileSystemScheme, - String depFilePath, - TargetModel targetModel = TargetModel.flutter, - }) async { - if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) { - printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,' - 'sdkRoot, packagesPath are not supported when using the experimental ' - 'build* pipeline'); - } - final BuildRunner buildRunner = buildRunnerFactory.create(); - try { - final BuildResult buildResult = await buildRunner.build( - aot: aot, - linkPlatformKernelIn: linkPlatformKernelIn, - trackWidgetCreation: trackWidgetCreation, - mainPath: mainPath, - targetProductVm: targetProductVm, - extraFrontEndOptions: extraFrontEndOptions - ); - final File outputFile = fs.file(outputFilePath); - if (!await outputFile.exists()) { - await outputFile.create(); - } - await outputFile.writeAsBytes(await buildResult.dillFile.readAsBytes()); - return CompilerOutput(outputFilePath, 0); - } on Exception catch (err) { - printError('Compilation Failed: $err'); - return const CompilerOutput(null, 1); - } - } -} diff --git a/packages/flutter_tools/lib/src/build_runner/build_runner.dart b/packages/flutter_tools/lib/src/build_runner/build_runner.dart index 68591c079b..90f660fcf0 100644 --- a/packages/flutter_tools/lib/src/build_runner/build_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/build_runner.dart @@ -4,69 +4,45 @@ import 'dart:async'; +import 'package:build_daemon/data/build_target.dart'; import 'package:build_runner_core/build_runner_core.dart'; +import 'package:build_daemon/data/server_log.dart'; +import 'package:build_daemon/data/build_status.dart' as build; +import 'package:build_daemon/client.dart'; import 'package:meta/meta.dart'; +import 'package:yaml/yaml.dart'; import '../artifacts.dart'; -import '../base/context.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; -import '../base/platform.dart'; import '../base/process_manager.dart'; import '../cache.dart'; +import '../codegen.dart'; import '../convert.dart'; import '../dart/pub.dart'; import '../globals.dart'; import '../project.dart'; +import '../resident_runner.dart'; import 'build_script_generator.dart'; -/// The [BuildRunnerFactory] instance. -BuildRunnerFactory get buildRunnerFactory => context[BuildRunnerFactory]; - -/// Whether to attempt to build a flutter project using build* libraries. -/// -/// This requires both an experimental opt in via the environment variable -/// 'FLUTTER_EXPERIMENTAL_BUILD' and that the project itself has a -/// dependency on the package 'flutter_build' and 'build_runner.' -bool get experimentalBuildEnabled { - return _experimentalBuildEnabled ??= platform.environment['FLUTTER_EXPERIMENTAL_BUILD']?.toLowerCase() == 'true'; -} -bool _experimentalBuildEnabled; - -@visibleForTesting -set experimentalBuildEnabled(bool value) { - _experimentalBuildEnabled = value; -} - -/// An injectable factory to create instances of [BuildRunner]. -class BuildRunnerFactory { - const BuildRunnerFactory(); - - /// Creates a new [BuildRunner] instance. - BuildRunner create() { - return BuildRunner(); - } -} - /// A wrapper for a build_runner process which delegates to a generated /// build script. /// /// This is only enabled if [experimentalBuildEnabled] is true, and only for /// external flutter users. -class BuildRunner { +class BuildRunner extends CodeGenerator { + const BuildRunner(); - /// Run a build_runner build and return the resulting .packages and dill file. - /// - /// The defines of the build command are the arguments required in the - /// flutter_build kernel builder. - Future build({ + @override + Future build({ + @required String mainPath, @required bool aot, @required bool linkPlatformKernelIn, @required bool trackWidgetCreation, @required bool targetProductVm, - @required String mainPath, - @required List extraFrontEndOptions, + List extraFrontEndOptions = const [], + bool disableKernelGeneration = false, }) async { await generateBuildScript(); final FlutterProject flutterProject = await FlutterProject.current(); @@ -95,7 +71,7 @@ class BuildRunner { '--packages=$scriptPackagesPath', buildScript, 'build', - '--define', 'flutter_build|kernel=disabled=false', + '--define', 'flutter_build|kernel=disabled=$disableKernelGeneration', '--define', 'flutter_build|kernel=aot=$aot', '--define', 'flutter_build|kernel=linkPlatformKernelIn=$linkPlatformKernelIn', '--define', 'flutter_build|kernel=trackWidgetCreation=$trackWidgetCreation', @@ -121,6 +97,9 @@ class BuildRunner { } finally { status.stop(); } + if (disableKernelGeneration) { + return const CodeGenerationResult(null, null); + } /// We don't check for this above because it might be generated for the /// first time by invoking the build. final Directory dartTool = flutterProject.dartTool; @@ -143,13 +122,10 @@ class BuildRunner { if (!packagesFile.existsSync() || !dillFile.existsSync()) { throw Exception('build_runner did not produce output at expected location: ${dillFile.path} missing'); } - return BuildResult(packagesFile, dillFile); + return CodeGenerationResult(packagesFile, dillFile); } - /// Invalidates a generated build script by deleting it. - /// - /// Must be called any time a pubspec file update triggers a corresponding change - /// in .packages. + @override Future invalidateBuildScript() async { final FlutterProject flutterProject = await FlutterProject.current(); final File buildScript = flutterProject.dartTool @@ -162,8 +138,7 @@ class BuildRunner { await buildScript.delete(); } - // Generates a synthetic package under .dart_tool/flutter_tool which is in turn - // used to generate a build script. + @override Future generateBuildScript() async { final FlutterProject flutterProject = await FlutterProject.current(); final String generatedDirectory = fs.path.join(flutterProject.dartTool.path, 'flutter_tool'); @@ -180,8 +155,10 @@ class BuildRunner { stringBuffer.writeln('name: synthetic_example'); stringBuffer.writeln('dependencies:'); - for (String builder in await flutterProject.builders) { - stringBuffer.writeln(' $builder: any'); + final YamlMap builders = await flutterProject.builders; + for (String name in builders.keys) { + final YamlNode node = builders[name]; + stringBuffer.writeln(' $name: $node'); } stringBuffer.writeln(' build_runner: any'); stringBuffer.writeln(' flutter_build:'); @@ -195,17 +172,92 @@ class BuildRunner { checkLastModified: false, ); final PackageGraph packageGraph = PackageGraph.forPath(syntheticPubspec.parent.path); - final BuildScriptGenerator buildScriptGenerator = buildScriptGeneratorFactory.create(flutterProject, packageGraph); + final BuildScriptGenerator buildScriptGenerator = const BuildScriptGeneratorFactory().create(flutterProject, packageGraph); await buildScriptGenerator.generateBuildScript(); } finally { status.stop(); } } + + @override + Future daemon({ + String mainPath, + bool linkPlatformKernelIn = false, + bool targetProductVm = false, + bool trackWidgetCreation = false, + List extraFrontEndOptions = const [], + }) async { + mainPath ??= findMainDartFile(); + await generateBuildScript(); + final FlutterProject flutterProject = await FlutterProject.current(); + final String frontendServerPath = artifacts.getArtifactPath( + Artifact.frontendServerSnapshotForEngineDartSdk + ); + final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath); + final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary); + final String packagesPath = flutterProject.packagesFile.absolute.path; + final String buildScript = flutterProject + .dartTool + .childDirectory('build') + .childDirectory('entrypoint') + .childFile('build.dart') + .path; + final String scriptPackagesPath = flutterProject + .dartTool + .childDirectory('flutter_tool') + .childFile('.packages') + .path; + final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart'); + final Status status = logger.startProgress('starting build daemon...', timeout: null); + BuildDaemonClient buildDaemonClient; + try { + final List command = [ + dartPath, + '--packages=$scriptPackagesPath', + buildScript, + 'daemon', + '--define', 'flutter_build|kernel=disabled=false', + '--define', 'flutter_build|kernel=aot=false', + '--define', 'flutter_build|kernel=linkPlatformKernelIn=$linkPlatformKernelIn', + '--define', 'flutter_build|kernel=trackWidgetCreation=$trackWidgetCreation', + '--define', 'flutter_build|kernel=targetProductVm=$targetProductVm', + '--define', 'flutter_build|kernel=mainPath=$mainPath', + '--define', 'flutter_build|kernel=packagesPath=$packagesPath', + '--define', 'flutter_build|kernel=sdkRoot=$sdkRoot', + '--define', 'flutter_build|kernel=frontendServerPath=$frontendServerPath', + '--define', 'flutter_build|kernel=engineDartBinaryPath=$engineDartBinaryPath', + '--define', 'flutter_build|kernel=extraFrontEndOptions=${extraFrontEndOptions ?? const []}', + ]; + buildDaemonClient = await BuildDaemonClient.connect(flutterProject.directory.path, command, logHandler: (ServerLog log) => printTrace(log.toString())); + } finally { + status.stop(); + } + buildDaemonClient.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder builder) { + builder.target = flutterProject.manifest.appName; + })); + final String relativeMain = fs.path.relative(mainPath, from: flutterProject.directory.path); + final File generatedPackagesFile = fs.file(fs.path.join(flutterProject.generated.path, fs.path.setExtension(relativeMain, '.packages'))); + final File generatedDillFile = fs.file(fs.path.join(flutterProject.generated.path, fs.path.setExtension(relativeMain, '.app.dill'))); + return _BuildRunnerCodegenDaemon(buildDaemonClient, generatedPackagesFile, generatedDillFile); + } } -class BuildResult { - const BuildResult(this.packagesFile, this.dillFile); +class _BuildRunnerCodegenDaemon implements CodegenDaemon { + _BuildRunnerCodegenDaemon(this.buildDaemonClient, this.packagesFile, this.dillFile); + final BuildDaemonClient buildDaemonClient; + @override final File packagesFile; + @override final File dillFile; + + @override + Stream get buildResults => buildDaemonClient.buildResults.map((build.BuildResults results) { + return results.results.first.status == build.BuildStatus.succeeded; + }); + + @override + void startBuild() { + buildDaemonClient.startBuild(); + } } diff --git a/packages/flutter_tools/lib/src/build_runner/build_script_generator.dart b/packages/flutter_tools/lib/src/build_runner/build_script_generator.dart index 1467872ace..cb951ca8fb 100644 --- a/packages/flutter_tools/lib/src/build_runner/build_script_generator.dart +++ b/packages/flutter_tools/lib/src/build_runner/build_script_generator.dart @@ -12,12 +12,9 @@ import 'package:dart_style/dart_style.dart'; import 'package:graphs/graphs.dart'; import '../base/common.dart'; -import '../base/context.dart'; import '../base/file_system.dart'; import '../project.dart'; -BuildScriptGeneratorFactory get buildScriptGeneratorFactory => context[BuildScriptGeneratorFactory]; - class BuildScriptGeneratorFactory { const BuildScriptGeneratorFactory(); @@ -227,14 +224,16 @@ class BuildScriptGenerator { return refer('toNoneByDefault', 'package:build_runner_core/build_runner_core.dart') .call([]); - case AutoApply.dependents: - return refer('toDependentsOf', - 'package:build_runner_core/build_runner_core.dart') - .call([literalString(definition.package)]); + // TODO(jonahwilliams): re-enabled when we have the builders strategy fleshed out. + // case AutoApply.dependents: + // return refer('toDependentsOf', + // 'package:build_runner_core/build_runner_core.dart') + // .call([literalString(definition.package)]); case AutoApply.allPackages: return refer('toAllPackages', 'package:build_runner_core/build_runner_core.dart') .call([]); + case AutoApply.dependents: case AutoApply.rootPackage: return refer('toRoot', 'package:build_runner_core/build_runner_core.dart') .call([]); diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart index 856d2196d5..2d810a8f55 100644 --- a/packages/flutter_tools/lib/src/bundle.dart +++ b/packages/flutter_tools/lib/src/bundle.dart @@ -99,7 +99,6 @@ Future build({ if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) printTrace('Extra front-end options: $extraFrontEndOptions'); ensureDirectoryExists(applicationKernelFilePath); - final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(); final CompilerOutput compilerOutput = await kernelCompiler.compile( sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), incrementalCompilerByteStorePath: compilationTraceFilePath != null ? null : diff --git a/packages/flutter_tools/lib/src/codegen.dart b/packages/flutter_tools/lib/src/codegen.dart new file mode 100644 index 0000000000..c0b32e6289 --- /dev/null +++ b/packages/flutter_tools/lib/src/codegen.dart @@ -0,0 +1,293 @@ +// Copyright 2019 The Chromium 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:meta/meta.dart'; + +import 'artifacts.dart'; +import 'base/context.dart'; +import 'base/file_system.dart'; +import 'base/platform.dart'; +import 'compile.dart'; +import 'globals.dart'; +import 'project.dart'; + +const String _kMultiRootScheme = 'org-dartlang-app'; + +/// The [CodeGenerator] instance. +/// +/// If [experimentalBuildEnabled] is false, this will contain an unsupported +/// implementation. +CodeGenerator get codeGenerator => context[CodeGenerator]; + +/// Whether to attempt to build a flutter project using build* libraries. +/// +/// This requires both an experimental opt in via the environment variable +/// 'FLUTTER_EXPERIMENTAL_BUILD' and that the project itself has a +/// dependency on the package 'flutter_build' and 'build_runner.' +bool get experimentalBuildEnabled { + return _experimentalBuildEnabled ??= platform.environment['FLUTTER_EXPERIMENTAL_BUILD']?.toLowerCase() == 'true'; +} +bool _experimentalBuildEnabled; + +@visibleForTesting +set experimentalBuildEnabled(bool value) { + _experimentalBuildEnabled = value; +} + +/// A wrapper for a build_runner process which delegates to a generated +/// build script. +/// +/// This is only enabled if [experimentalBuildEnabled] is true, and only for +/// external flutter users. +abstract class CodeGenerator { + const CodeGenerator(); + + /// Run a partial build include code generators but not kernel. + Future generate({@required String mainPath}) async { + await build( + mainPath: mainPath, + aot: false, + linkPlatformKernelIn: false, + trackWidgetCreation: false, + targetProductVm: false, + disableKernelGeneration: true, + ); + } + + /// Run a full build and return the resulting .packages and dill file. + /// + /// The defines of the build command are the arguments required in the + /// flutter_build kernel builder. + Future build({ + @required String mainPath, + @required bool aot, + @required bool linkPlatformKernelIn, + @required bool trackWidgetCreation, + @required bool targetProductVm, + List extraFrontEndOptions = const [], + bool disableKernelGeneration = false, + }); + + /// Starts a persistent code generting daemon. + /// + /// The defines of the daemon command are the arguments required in the + /// flutter_build kernel builder. + Future daemon({ + @required String mainPath, + bool linkPlatformKernelIn = false, + bool targetProductVm = false, + bool trackWidgetCreation = false, + List extraFrontEndOptions = const [], + }); + + /// Invalidates a generated build script by deleting it. + /// + /// Must be called any time a pubspec file update triggers a corresponding change + /// in .packages. + Future invalidateBuildScript(); + + // Generates a synthetic package under .dart_tool/flutter_tool which is in turn + // used to generate a build script. + Future generateBuildScript(); +} + +class UnsupportedCodeGenerator extends CodeGenerator { + const UnsupportedCodeGenerator(); + + @override + Future build({ + String mainPath, + bool aot, + bool linkPlatformKernelIn, + bool trackWidgetCreation, + bool targetProductVm, + List extraFrontEndOptions = const [], + bool disableKernelGeneration = false, + }) { + throw UnsupportedError('build_runner is not currently supported.'); + } + + @override + Future generateBuildScript() { + throw UnsupportedError('build_runner is not currently supported.'); + } + + @override + Future invalidateBuildScript() { + throw UnsupportedError('build_runner is not currently supported.'); + } + + @override + Future daemon({ + String mainPath, + bool linkPlatformKernelIn = false, + bool targetProductVm = false, + bool trackWidgetCreation = false, + List extraFrontEndOptions = const [], + }) { + throw UnsupportedError('build_runner is not currently supported.'); + } +} + +abstract class CodegenDaemon { + /// Whether the previously enqueued build was successful. + Stream get buildResults; + + /// Starts a new build. + void startBuild(); + + File get packagesFile; + + File get dillFile; +} + +/// The result of running a build through a [CodeGenerator]. +/// +/// If no dill or packages file is generated, they will be null. +class CodeGenerationResult { + const CodeGenerationResult(this.packagesFile, this.dillFile); + + final File packagesFile; + final File dillFile; +} + +/// An implementation of the [KernelCompiler] which delegates to build_runner. +/// +/// Only a subset of the arguments provided to the [KernelCompiler] are +/// supported here. Using the build pipeline implies a fixed multiroot +/// filesystem and requires a pubspec. +/// +/// This is only safe to use if [experimentalBuildEnabled] is true. +class CodeGeneratingKernelCompiler implements KernelCompiler { + const CodeGeneratingKernelCompiler(); + + @override + Future compile({ + String mainPath, + String outputFilePath, + bool linkPlatformKernelIn = false, + bool aot = false, + bool trackWidgetCreation, + List extraFrontEndOptions, + String incrementalCompilerByteStorePath, + bool targetProductVm = false, + // These arguments are currently unused. + String sdkRoot, + String packagesPath, + List fileSystemRoots, + String fileSystemScheme, + String depFilePath, + TargetModel targetModel = TargetModel.flutter, + }) async { + if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) { + printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,' + 'sdkRoot, packagesPath are not supported when using the experimental ' + 'build* pipeline'); + } + try { + final CodeGenerationResult buildResult = await codeGenerator.build( + aot: aot, + linkPlatformKernelIn: linkPlatformKernelIn, + trackWidgetCreation: trackWidgetCreation, + mainPath: mainPath, + targetProductVm: targetProductVm, + extraFrontEndOptions: extraFrontEndOptions + ); + final File outputFile = fs.file(outputFilePath); + if (!await outputFile.exists()) { + await outputFile.create(); + } + await outputFile.writeAsBytes(await buildResult.dillFile.readAsBytes()); + return CompilerOutput(outputFilePath, 0); + } on Exception catch (err) { + printError('Compilation Failed: $err'); + return const CompilerOutput(null, 1); + } + } +} + +/// An implementation of a [ResidentCompiler] which runs a [BuildRunner] before +/// talking to the CFE. +class CodeGeneratingResidentCompiler implements ResidentCompiler { + CodeGeneratingResidentCompiler._(this._residentCompiler, this._codegenDaemon); + + /// Creates a new [ResidentCompiler] and configures a [BuildDaemonClient] to + /// run builds. + static Future create({ + @required String mainPath, + bool trackWidgetCreation = false, + CompilerMessageConsumer compilerMessageConsumer = printError, + bool unsafePackageSerialization = false, + }) async { + final FlutterProject flutterProject = await FlutterProject.current(); + final CodegenDaemon codegenDaemon = await codeGenerator.daemon( + extraFrontEndOptions: [], + linkPlatformKernelIn: false, + mainPath: mainPath, + targetProductVm: false, + trackWidgetCreation: trackWidgetCreation, + ); + codegenDaemon.startBuild(); + await codegenDaemon.buildResults.firstWhere((bool result) => result); + final ResidentCompiler residentCompiler = ResidentCompiler( + artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), + trackWidgetCreation: trackWidgetCreation, + packagesPath: codegenDaemon.packagesFile.path, + fileSystemRoots: [ + flutterProject.generated.absolute.path, + flutterProject.directory.path, + ], + fileSystemScheme: _kMultiRootScheme, + targetModel: TargetModel.flutter, + unsafePackageSerialization: unsafePackageSerialization, + ); + return CodeGeneratingResidentCompiler._(residentCompiler, codegenDaemon); + } + + final ResidentCompiler _residentCompiler; + final CodegenDaemon _codegenDaemon; + + @override + void accept() { + _residentCompiler.accept(); + } + + @override + Future compileExpression(String expression, List definitions, List typeDefinitions, String libraryUri, String klass, bool isStatic) { + return _residentCompiler.compileExpression(expression, definitions, typeDefinitions, libraryUri, klass, isStatic); + } + + @override + Future recompile(String mainPath, List invalidatedFiles, {String outputPath, String packagesFilePath}) async { + _codegenDaemon.startBuild(); + await _codegenDaemon.buildResults.first; + // Delete this file so that the frontend_server can handle multi-root. + // TODO(jonahwilliams): investigate frontend_server behavior in the presence + // of multi-root and initialize from dill. + if (await fs.file(outputPath).exists()) { + await fs.file(outputPath).delete(); + } + return _residentCompiler.recompile( + mainPath, + invalidatedFiles, + outputPath: outputPath, + packagesFilePath: _codegenDaemon.packagesFile.path, + ); + } + + @override + Future reject() { + return _residentCompiler.reject(); + } + + @override + void reset() { + _residentCompiler.reset(); + } + + @override + Future shutdown() { + return _residentCompiler.shutdown(); + } +} diff --git a/packages/flutter_tools/lib/src/commands/generate.dart b/packages/flutter_tools/lib/src/commands/generate.dart new file mode 100644 index 0000000000..71b0a8a514 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/generate.dart @@ -0,0 +1,30 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../base/common.dart'; +import '../codegen.dart'; +import '../runner/flutter_command.dart'; + +class GenerateCommand extends FlutterCommand { + GenerateCommand() { + usesTargetOption(); + } + @override + String get description => 'run code generators.'; + + @override + String get name => 'generate'; + + @override + bool get hidden => true; + + @override + Future runCommand() async { + if (!experimentalBuildEnabled) { + throwToolExit('FLUTTER_EXPERIMENTAL_BUILD is not enabled, codegen is unsupported.'); + } + await codeGenerator.generate(mainPath: argResults['target']); + return null; + } +} \ No newline at end of file diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 1a31645b09..6c59ab0764 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -10,6 +10,8 @@ import '../base/time.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; +import '../codegen.dart'; +import '../compile.dart'; import '../device.dart'; import '../globals.dart'; import '../ios/mac.dart'; @@ -345,6 +347,10 @@ class RunCommand extends RunCommandBase { expFlags = argResults[FlutterOptions.kEnableExperiment]; } + ResidentCompiler residentCompiler; + if (experimentalBuildEnabled) { + residentCompiler = await CodeGeneratingResidentCompiler.create(mainPath: argResults['target']); + } final List flutterDevices = devices.map((Device device) { return FlutterDevice( device, @@ -354,6 +360,7 @@ class RunCommand extends RunCommandBase { fileSystemScheme: argResults['filesystem-scheme'], viewFilter: argResults['isolate-filter'], experimentalFlags: expFlags, + generator: residentCompiler, ); }).toList(); diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 07aab13a7e..5f7c5d8738 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -390,7 +390,7 @@ class _DependencyLink { /// "dependency_overrides" sections, as well as the "name" and "version" fields /// in the pubspec header bucketed into [header]. The others are all bucketed /// into [other]. -enum Section { header, dependencies, devDependencies, dependencyOverrides, other } +enum Section { header, dependencies, devDependencies, dependencyOverrides, builders, other } /// The various kinds of dependencies we know and care about. enum DependencyKind { @@ -504,6 +504,11 @@ class PubspecYaml { seenDev = true; } result.add(header); + } else if (section == Section.builders) { + // Do nothing. + // This line isn't a section header, and we're not in a section we care about. + // We just stick the line into the output unmodified. + result.add(PubspecLine(line)); } else if (section == Section.other) { if (line.contains(kDependencyChecksum)) { // This is the pubspec checksum. After computing it, we remove it from the output data @@ -878,6 +883,8 @@ class PubspecHeader extends PubspecLine { return PubspecHeader(line, Section.devDependencies); case 'dependency_overrides': return PubspecHeader(line, Section.dependencyOverrides); + case 'builders': + return PubspecHeader(line, Section.builders); case 'name': case 'version': return PubspecHeader(line, Section.header, name: sectionName, value: value); diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index e247969688..8fa4ed74aa 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -20,21 +20,10 @@ import 'convert.dart'; import 'dart/package_map.dart'; import 'globals.dart'; -KernelCompilerFactory get kernelCompilerFactory => context[KernelCompilerFactory]; +KernelCompiler get kernelCompiler => context[KernelCompiler]; typedef CompilerMessageConsumer = void Function(String message, {bool emphasis, TerminalColor color}); -/// Injectable factory to allow async construction of a [KernelCompiler]. -class KernelCompilerFactory { - const KernelCompilerFactory(); - - /// Return the correct [KernelCompiler] instance for the given project - /// configuration. - FutureOr create() async { - return const KernelCompiler(); - } -} - /// The target model describes the set of core libraries that are availible within /// the SDK. class TargetModel { diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 7a4490baf1..af2976ddc2 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -22,6 +22,7 @@ import 'base/time.dart'; import 'base/user_messages.dart'; import 'base/utils.dart'; import 'cache.dart'; +import 'codegen.dart'; import 'compile.dart'; import 'devfs.dart'; import 'device.dart'; @@ -80,7 +81,7 @@ Future runInContext( IOSSimulatorUtils: () => IOSSimulatorUtils(), IOSWorkflow: () => const IOSWorkflow(), IOSValidator: () => const IOSValidator(), - KernelCompilerFactory: () => const KernelCompilerFactory(), + KernelCompiler: () => experimentalBuildEnabled ? const CodeGeneratingKernelCompiler() : const KernelCompiler(), LinuxWorkflow: () => const LinuxWorkflow(), Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(), MacOSWorkflow: () => const MacOSWorkflow(), diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 04e47a81ba..dc7587c1e4 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -107,6 +107,13 @@ class FlutterProject { /// The `.dart-tool` directory of this project. Directory get dartTool => directory.childDirectory('.dart_tool'); + /// The directory containing the generated code for this project. + Directory get generated => directory + .childDirectory('.dart_tool') + .childDirectory('build') + .childDirectory('generated') + .childDirectory(manifest.appName); + /// The example sub-project of this project. FlutterProject get example => FlutterProject( _exampleDirectory(directory), @@ -147,15 +154,9 @@ class FlutterProject { } /// Return the set of builders used by this package. - Future> get builders async { + Future get builders async { final YamlMap pubspec = loadYaml(await pubspecFile.readAsString()); - final YamlList builders = pubspec['builders']; - if (builders == null) { - return []; - } - return builders.map((Object node) { - return node.toString(); - }).toList(); + return pubspec['builders']; } } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 788cdee24b..576240d153 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -16,6 +16,7 @@ import 'base/logger.dart'; import 'base/terminal.dart'; import 'base/utils.dart'; import 'build_info.dart'; +import 'codegen.dart'; import 'compile.dart'; import 'dart/dependencies.dart'; import 'dart/package_map.dart'; @@ -895,6 +896,11 @@ abstract class ResidentRunner { } bool hasDirtyDependencies(FlutterDevice device) { + /// When using the build system, dependency analysis is handled by build + /// runner instead. + if (experimentalBuildEnabled) { + return false; + } final DartDependencySetBuilder dartDependencySetBuilder = DartDependencySetBuilder(mainPath, packagesFilePath); final DependencyChecker dependencyChecker = diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 27541dd307..116768ae68 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -15,6 +15,7 @@ import 'base/logger.dart'; import 'base/terminal.dart'; import 'base/utils.dart'; import 'build_info.dart'; +import 'codegen.dart'; import 'compile.dart'; import 'convert.dart'; import 'dart/dependencies.dart'; @@ -122,8 +123,12 @@ class HotRunner extends ResidentRunner { return false; } - final DartDependencySetBuilder dartDependencySetBuilder = - DartDependencySetBuilder(mainPath, packagesFilePath); + /// When using the build system, dependency analysis is handled by build + /// runner instead. + if (experimentalBuildEnabled) { + return true; + } + final DartDependencySetBuilder dartDependencySetBuilder = DartDependencySetBuilder(mainPath, packagesFilePath); try { _dartDependencies = Set.from(dartDependencySetBuilder.build()); } on DartDependencyException catch (error) { diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index e3cd1e881b..35ec21c0ce 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -39,10 +39,6 @@ dependencies: flutter_goldens_client: path: ../flutter_goldens_client - # build_runner depenencies needed for codegen. - build: 1.1.1 - build_modules: 1.0.7 - # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading # this, make sure the tests are still running correctly. @@ -53,6 +49,9 @@ dependencies: build_runner_core: 2.0.3 dart_style: 1.2.3 code_builder: 3.2.0 + build: 1.1.1 + build_modules: 1.0.7+2 + build_daemon: 0.4.0 async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" bazel_worker: 0.1.20 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -85,9 +84,12 @@ dependencies: pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pubspec_parse: 0.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" scratch_space: 0.0.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.5.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_transform: 0.0.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" timing: 0.1.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -106,14 +108,12 @@ dev_dependencies: mime: 0.9.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 1.4.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf: 0.7.4 # 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_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_web_socket: 0.2.2+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" test: 1.5.3 # 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: c962 +# PUBSPEC CHECKSUM: 6c3e diff --git a/packages/flutter_tools/test/build_runner/build_runner_test.dart b/packages/flutter_tools/test/build_runner/build_runner_test.dart index a69ee5f3c5..fb7575a22a 100644 --- a/packages/flutter_tools/test/build_runner/build_runner_test.dart +++ b/packages/flutter_tools/test/build_runner/build_runner_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/build_runner/build_runner.dart'; +import 'package:flutter_tools/src/codegen.dart'; import 'package:mockito/mockito.dart'; import '../src/common.dart'; diff --git a/packages/flutter_tools/test/build_runner/build_kernel_compiler_test.dart b/packages/flutter_tools/test/build_runner/code_generating_kernel_compiler.dart similarity index 80% rename from packages/flutter_tools/test/build_runner/build_kernel_compiler_test.dart rename to packages/flutter_tools/test/build_runner/code_generating_kernel_compiler.dart index f6ccad300c..4dbd4d558f 100644 --- a/packages/flutter_tools/test/build_runner/build_kernel_compiler_test.dart +++ b/packages/flutter_tools/test/build_runner/code_generating_kernel_compiler.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/build_runner/build_kernel_compiler.dart'; import 'package:flutter_tools/src/build_runner/build_runner.dart'; +import 'package:flutter_tools/src/codegen.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:mockito/mockito.dart'; @@ -12,8 +12,7 @@ import '../src/common.dart'; import '../src/context.dart'; void main() { - group(BuildKernelCompiler, () { - final MockBuildRunnerFactory mockBuildRunnerFactory = MockBuildRunnerFactory(); + group(CodeGeneratingKernelCompiler, () { final MockBuildRunner mockBuildRunner = MockBuildRunner(); final MockFileSystem mockFileSystem = MockFileSystem(); final MockFile packagesFile = MockFile(); @@ -26,11 +25,10 @@ void main() { when(packagesFile.exists()).thenAnswer((Invocation invocation) async => true); when(dillFile.exists()).thenAnswer((Invocation invocation) async => true); when(outputFile.exists()).thenAnswer((Invocation invocation) async => true); - when(mockBuildRunnerFactory.create()).thenReturn(mockBuildRunner); when(dillFile.readAsBytes()).thenAnswer((Invocation invocation) async => [0, 1, 2, 3]); testUsingContext('delegates to build_runner', () async { - const BuildKernelCompiler kernelCompiler = BuildKernelCompiler(); + const CodeGeneratingKernelCompiler kernelCompiler = CodeGeneratingKernelCompiler(); when(mockBuildRunner.build( aot: anyNamed('aot'), extraFrontEndOptions: anyNamed('extraFrontEndOptions'), @@ -39,7 +37,7 @@ void main() { targetProductVm: anyNamed('targetProductVm'), trackWidgetCreation: anyNamed('trackWidgetCreation') )).thenAnswer((Invocation invocation) async { - return BuildResult(fs.file('.packages'), fs.file('main.app.dill')); + return CodeGenerationResult(fs.file('.packages'), fs.file('main.app.dill')); }); final CompilerOutput buildResult = await kernelCompiler.compile( outputFilePath: 'output.app.dill', @@ -48,13 +46,12 @@ void main() { expect(buildResult.errorCount, 0); verify(outputFile.writeAsBytes([0, 1, 2, 3])).called(1); }, overrides: { - BuildRunnerFactory: () => mockBuildRunnerFactory, + CodeGenerator: () => mockBuildRunner, FileSystem: () => mockFileSystem, }); }); } -class MockBuildRunnerFactory extends Mock implements BuildRunnerFactory {} class MockBuildRunner extends Mock implements BuildRunner {} class MockFileSystem extends Mock implements FileSystem {} class MockFile extends Mock implements File {} diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart index 79f9d267e0..ba7ae68a6c 100644 --- a/packages/flutter_tools/test/compile_test.dart +++ b/packages/flutter_tools/test/compile_test.dart @@ -118,7 +118,6 @@ example:org-dartlang-app:/ 'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0' )) )); - final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(); final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', trackWidgetCreation: false, @@ -142,7 +141,6 @@ example:org-dartlang-app:/ 'result abc\nline1\nline2\nabc' )) )); - final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(); final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', trackWidgetCreation: false, @@ -168,7 +166,6 @@ example:org-dartlang-app:/ 'result abc\nline1\nline2\nabc' )) )); - final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(); final CompilerOutput output = await kernelCompiler.compile( sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', diff --git a/packages/flutter_tools/test/forbidden_imports_test.dart b/packages/flutter_tools/test/forbidden_imports_test.dart index 3b3c3c2e23..4a6fa029ac 100644 --- a/packages/flutter_tools/test/forbidden_imports_test.dart +++ b/packages/flutter_tools/test/forbidden_imports_test.dart @@ -78,6 +78,34 @@ void main() { } } }); + + test('no unauthorized imports of build_runner', () { + final List whitelistedPaths = [ + fs.path.join(flutterTools, 'test', 'src', 'build_runner'), + fs.path.join(flutterTools, 'lib', 'src', 'build_runner'), + fs.path.join(flutterTools, 'lib', 'executable.dart') + ]; + bool _isNotWhitelisted(FileSystemEntity entity) => whitelistedPaths.every((String path) => !entity.path.contains(path)); + + for (String dirName in ['lib']) { + final Iterable files = fs.directory(fs.path.join(flutterTools, dirName)) + .listSync(recursive: true) + .where(_isDartFile) + .where(_isNotWhitelisted) + .map(_asFile); + for (File file in files) { + for (String line in file.readAsLinesSync()) { + if (line.startsWith(RegExp(r'import.*package:build_runner_core/build_runner_core.dart')) || + line.startsWith(RegExp(r'import.*package:build_runner/build_runner.dart')) || + line.startsWith(RegExp(r'import.*package:build_config/build_config.dart')) || + line.startsWith(RegExp(r'import.*build_runner/.*.dart'))) { + final String relativePath = fs.path.relative(file.path, from:flutterTools); + fail('$relativePath imports a build_runner package'); + } + } + } + } + }); } bool _isDartFile(FileSystemEntity entity) => entity is File && entity.path.endsWith('.dart'); diff --git a/packages/flutter_tools/test/tester/flutter_tester_test.dart b/packages/flutter_tools/test/tester/flutter_tester_test.dart index 3d9961ed07..5b7aa26d8c 100644 --- a/packages/flutter_tools/test/tester/flutter_tester_test.dart +++ b/packages/flutter_tools/test/tester/flutter_tester_test.dart @@ -102,7 +102,6 @@ void main() { MockArtifacts mockArtifacts; MockKernelCompiler mockKernelCompiler; MockProcessManager mockProcessManager; - MockKernelCompilerFactory mockKernelCompilerFactory; MockProcess mockProcess; final Map startOverrides = { @@ -110,7 +109,7 @@ void main() { FileSystem: () => fs, Cache: () => Cache(rootOverride: fs.directory(flutterRoot)), ProcessManager: () => mockProcessManager, - KernelCompilerFactory: () => mockKernelCompilerFactory, + KernelCompiler: () => mockKernelCompiler, Artifacts: () => mockArtifacts, }; @@ -136,10 +135,6 @@ void main() { when(mockArtifacts.getArtifactPath(any)).thenReturn(artifactPath); mockKernelCompiler = MockKernelCompiler(); - mockKernelCompilerFactory = MockKernelCompilerFactory(); - when(mockKernelCompilerFactory.create()).thenAnswer((Invocation invocation) async { - return mockKernelCompiler; - }); }); testUsingContext('not debug', () async { @@ -199,4 +194,3 @@ Hello! class MockArtifacts extends Mock implements Artifacts {} class MockKernelCompiler extends Mock implements KernelCompiler {} -class MockKernelCompilerFactory extends Mock implements KernelCompilerFactory {} From 7862bef13a1655d8adc63ea7748b74fb7c21773a Mon Sep 17 00:00:00 2001 From: Martin Kustermann Date: Fri, 15 Feb 2019 14:10:55 +0100 Subject: [PATCH 008/395] Use double literals where a double type is expected (#27929) This makes the code more consistent but also fixes our flutter-analyze bot. Issue https://github.com/dart-lang/sdk/issues/35940 --- packages/flutter/lib/src/animation/curves.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index d5aac3d00a..2043527d3d 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -569,7 +569,7 @@ class Curves { /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4} - static const Cubic easeInSine = Cubic(0.47, 0, 0.745, 0.715); + static const Cubic easeInSine = Cubic(0.47, 0.0, 0.745, 0.715); /// A cubic animation curve that starts slowly and ends quickly. Based on a /// quadratic equation where `f(t) = t²`, this is effectively the inverse of @@ -802,7 +802,7 @@ class Curves { /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4} - static const Cubic easeInOutQuart = Cubic(0.77, 0, 0.175, 1.0); + static const Cubic easeInOutQuart = Cubic(0.77, 0.0, 0.175, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then and ends /// slowly. This curve can be imagined as [Curves.easeInQuint] as the first @@ -813,7 +813,7 @@ class Curves { /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4} - static const Cubic easeInOutQuint = Cubic(0.86, 0, 0.07, 1.0); + static const Cubic easeInOutQuint = Cubic(0.86, 0.0, 0.07, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then and ends /// slowly. @@ -827,7 +827,7 @@ class Curves { /// Derived from Robert Penner’s easing functions. /// /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4} - static const Cubic easeInOutExpo = Cubic(1.0, 0, 0, 1.0); + static const Cubic easeInOutExpo = Cubic(1.0, 0.0, 0.0, 1.0); /// A cubic animation curve that starts slowly, speeds up, and then and ends /// slowly. This curve can be imagined as [Curves.easeInCirc] as the first From 262f12b4a9269a77bce499926afa681b20790d6c Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 15 Feb 2019 07:48:49 -0800 Subject: [PATCH 009/395] Remove remaining "## Sample code" segments, and fix the snippet generator. (#27793) This converts all remaining "## Sample code" segments into snippets, and fixes the snippet generator to handle multiple snippets in the same dartdoc block properly. I also generated, compiled, and ran each of the existing application samples, and fixed them up to be more useful and/or just run without errors. This PR fixes these problems with examples: 1. Switching tabs in a snippet now works if there is more than one snippet in a single dartdoc block. 2. Generation of snippet code now works if there is more than one snippet. 3. Contrast of text and links in the code sample block has been improved to recommended levels. 4. Added five new snippet templates, including a "freeform" template to make it possible to show examples that need to change the app instantiation. 5. Fixed several examples to run properly, a couple by adding the "Scaffold" widget to the template, a couple by just fixing their code. 6. Fixed visual look of some of the samples when they run by placing many samples inside of a Scaffold. 7. In order to make it easier to run locally, changed the sample analyzer to remove the contents of the supplied temp directory before running, since having files that hang around is problematic (only a problem when running locally with the `--temp` argument). 8. Added a `SampleCheckerException` class, and handle sample checking exceptions more gracefully. 9. Deprecated the old "## Sample code" designation, and added enforcement for the deprecation. 10. Removed unnecessary `new` from templates (although they never appeared in the samples thanks to dartfmt, but still). Fixes #26398 Fixes #27411 --- dev/bots/analyze-sample-code.dart | 178 +++++++++++------- .../known_broken_documentation.dart | 8 +- dev/bots/test/analyze-sample-code_test.dart | 4 +- dev/docs/assets/snippets.css | 21 ++- dev/docs/assets/snippets.js | 14 +- dev/snippets/README.md | 4 +- .../config/skeletons/application.html | 16 +- dev/snippets/config/skeletons/sample.html | 2 +- dev/snippets/config/templates/README.md | 27 ++- dev/snippets/config/templates/freeform.tmpl | 11 ++ .../config/templates/stateful_widget.tmpl | 20 +- .../templates/stateful_widget_material.tmpl | 35 ++++ .../templates/stateful_widget_scaffold.tmpl | 38 ++++ .../config/templates/stateless_widget.tmpl | 23 +-- .../templates/stateless_widget_material.tmpl | 33 ++++ .../templates/stateless_widget_scaffold.tmpl | 36 ++++ dev/snippets/lib/main.dart | 15 +- dev/snippets/lib/snippets.dart | 26 ++- dev/tools/gen_keycodes/data/keyboard_key.tmpl | 4 +- .../flutter/lib/src/material/app_bar.dart | 33 ++-- .../src/material/bottom_navigation_bar.dart | 73 ++++--- packages/flutter/lib/src/material/card.dart | 82 ++++---- packages/flutter/lib/src/material/chip.dart | 4 +- .../flutter/lib/src/material/dropdown.dart | 2 +- .../flutter/lib/src/material/icon_button.dart | 34 ++-- .../lib/src/material/raised_button.dart | 62 +++--- .../flutter/lib/src/material/scaffold.dart | 92 ++++++--- .../flutter/lib/src/material/stepper.dart | 67 +++---- .../flutter/lib/src/rendering/binding.dart | 17 +- .../flutter/lib/src/semantics/semantics.dart | 31 ++- .../lib/src/services/keyboard_key.dart | 4 +- .../lib/src/services/system_chrome.dart | 69 ++++--- packages/flutter/lib/src/widgets/basic.dart | 49 +++-- .../flutter/lib/src/widgets/navigator.dart | 146 ++++++++++---- .../src/widgets/single_child_scroll_view.dart | 141 +++++++------- 35 files changed, 909 insertions(+), 512 deletions(-) create mode 100644 dev/snippets/config/templates/freeform.tmpl create mode 100644 dev/snippets/config/templates/stateful_widget_material.tmpl create mode 100644 dev/snippets/config/templates/stateful_widget_scaffold.tmpl create mode 100644 dev/snippets/config/templates/stateless_widget_material.tmpl create mode 100644 dev/snippets/config/templates/stateless_widget_scaffold.tmpl diff --git a/dev/bots/analyze-sample-code.dart b/dev/bots/analyze-sample-code.dart index a749dd2c71..6af14703f9 100644 --- a/dev/bots/analyze-sample-code.dart +++ b/dev/bots/analyze-sample-code.dart @@ -87,12 +87,42 @@ void main(List arguments) { Directory tempDirectory; if (parsedArguments.wasParsed('temp')) { - tempDirectory = Directory(parsedArguments['temp']); - if (!tempDirectory.existsSync()) { - tempDirectory.createSync(recursive: true); + tempDirectory = Directory(path.join(Directory.systemTemp.absolute.path, path.basename(parsedArguments['temp']))); + if (path.basename(parsedArguments['temp']) != parsedArguments['temp']) { + stderr.writeln('Supplied temporary directory name should be a name, not a path. Using ${tempDirectory.absolute.path} instead.'); + } + print('Leaving temporary output in ${tempDirectory.absolute.path}.'); + // Make sure that any directory left around from a previous run is cleared + // out. + if (tempDirectory.existsSync()) { + tempDirectory.deleteSync(recursive: true); + } + tempDirectory.createSync(); + } + try { + exitCode = SampleChecker(flutterPackage, tempDirectory: tempDirectory).checkSamples(); + } on SampleCheckerException catch (e) { + stderr.write(e); + exit(1); + } +} + +class SampleCheckerException implements Exception { + SampleCheckerException(this.message, {this.file, this.line}); + final String message; + final String file; + final int line; + + @override + String toString() { + if (file != null || line != null) { + final String fileStr = file == null ? '' : '$file:'; + final String lineStr = line == null ? '' : '$line:'; + return '$fileStr$lineStr Error: $message'; + } else { + return 'Error: $message'; } } - exitCode = SampleChecker(flutterPackage, tempDirectory: tempDirectory).checkSamples(); } /// Checks samples and code snippets for analysis errors. @@ -129,10 +159,10 @@ class SampleChecker { static final RegExp _dartDocSampleEndRegex = RegExp(r'{@end-tool}'); /// A RegExp that matches the start of a code block within dartdoc. - static final RegExp _codeBlockStartRegex = RegExp(r'/// ```dart.*$'); + static final RegExp _codeBlockStartRegex = RegExp(r'///\s+```dart.*$'); /// A RegExp that matches the end of a code block within dartdoc. - static final RegExp _codeBlockEndRegex = RegExp(r'/// ```\s*$'); + static final RegExp _codeBlockEndRegex = RegExp(r'///\s+```\s*$'); /// A RegExp that matches a Dart constructor. static final RegExp _constructorRegExp = RegExp(r'[A-Z][a-zA-Z0-9<>.]*\('); @@ -173,10 +203,7 @@ class SampleChecker { } static List _listDartFiles(Directory directory, {bool recursive = false}) { - return directory.listSync(recursive: recursive, followLinks: false) - .whereType() - .where((File file) => path.extension(file.path) == '.dart') - .toList(); + return directory.listSync(recursive: recursive, followLinks: false).whereType().where((File file) => path.extension(file.path) == '.dart').toList(); } /// Computes the headers needed for each sample file. @@ -218,14 +245,14 @@ class SampleChecker { } stderr.writeln('\nFound ${errors.length} sample code errors.'); } - if (!_keepTmp) { + if (_keepTmp) { + print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.'); + } else { try { _tempDirectory.deleteSync(recursive: true); } on FileSystemException catch (e) { stderr.writeln('Failed to delete ${_tempDirectory.path}: $e'); } - } else { - print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.'); } // If we made a snapshot, remove it (so as not to clutter up the tree). if (_snippetsSnapshotPath != null) { @@ -288,8 +315,12 @@ class SampleChecker { print('Generating snippet for ${snippet.start?.filename}:${snippet.start?.line}'); final ProcessResult process = _runSnippetsScript(args); if (process.exitCode != 0) { - throw 'Unable to create snippet for ${snippet.start.filename}:${snippet.start.line} ' - '(using input from ${inputFile.path}):\n${process.stdout}\n${process.stderr}'; + throw SampleCheckerException( + 'Unable to create snippet for ${snippet.start.filename}:${snippet.start.line} ' + '(using input from ${inputFile.path}):\n${process.stdout}\n${process.stderr}', + file: snippet.start.filename, + line: snippet.start.line, + ); } return outputFile; } @@ -304,11 +335,14 @@ class SampleChecker { final String relativeFilePath = path.relative(file.path, from: _flutterPackage.path); final List sampleLines = file.readAsLinesSync(); final List
preambleSections =
[]; + // Whether or not we're in the file-wide preamble section ("Examples can assume"). bool inPreamble = false; + // Whether or not we're in a code sample bool inSampleSection = false; + // Whether or not we're in a snippet code sample (with template) specifically. bool inSnippet = false; + // Whether or not we're in a '```dart' segment. bool inDart = false; - bool foundDart = false; int lineNumber = 0; final List block = []; List snippetArgs = []; @@ -318,7 +352,7 @@ class SampleChecker { final String trimmedLine = line.trim(); if (inSnippet) { if (!trimmedLine.startsWith(_dartDocPrefix)) { - throw '$relativeFilePath:$lineNumber: Snippet section unterminated.'; + throw SampleCheckerException('Snippet section unterminated.', file: relativeFilePath, line: lineNumber); } if (_dartDocSampleEndRegex.hasMatch(trimmedLine)) { snippets.add( @@ -342,53 +376,51 @@ class SampleChecker { preambleSections.add(_processBlock(startLine, block)); block.clear(); } else if (!line.startsWith('// ')) { - throw '$relativeFilePath:$lineNumber: Unexpected content in sample code preamble.'; + throw SampleCheckerException('Unexpected content in sample code preamble.', file: relativeFilePath, line: lineNumber); } else { block.add(line.substring(3)); } } else if (inSampleSection) { - if (!trimmedLine.startsWith(_dartDocPrefix) || trimmedLine.startsWith('$_dartDocPrefix ## ')) { + if (_dartDocSampleEndRegex.hasMatch(trimmedLine)) { if (inDart) { - throw '$relativeFilePath:$lineNumber: Dart section inexplicably unterminated.'; - } - if (!foundDart) { - throw '$relativeFilePath:$lineNumber: No dart block found in sample code section'; + throw SampleCheckerException("Dart section didn't terminate before end of sample", file: relativeFilePath, line: lineNumber); } inSampleSection = false; - } else { - if (inDart) { - if (_codeBlockEndRegex.hasMatch(trimmedLine)) { - inDart = false; - final Section processed = _processBlock(startLine, block); - if (preambleSections.isEmpty) { - sections.add(processed); - } else { - sections.add(Section.combine(preambleSections - ..toList() - ..add(processed))); - } - block.clear(); - } else if (trimmedLine == _dartDocPrefix) { - block.add(''); + } + if (inDart) { + if (_codeBlockEndRegex.hasMatch(trimmedLine)) { + inDart = false; + final Section processed = _processBlock(startLine, block); + if (preambleSections.isEmpty) { + sections.add(processed); } else { - final int index = line.indexOf(_dartDocPrefixWithSpace); - if (index < 0) { - throw '$relativeFilePath:$lineNumber: Dart section inexplicably did not ' - 'contain "$_dartDocPrefixWithSpace" prefix.'; - } - block.add(line.substring(index + 4)); + sections.add(Section.combine(preambleSections + ..toList() + ..add(processed))); } - } else if (_codeBlockStartRegex.hasMatch(trimmedLine)) { - assert(block.isEmpty); - startLine = Line( - '', - filename: relativeFilePath, - line: lineNumber + 1, - indent: line.indexOf(_dartDocPrefixWithSpace) + _dartDocPrefixWithSpace.length, - ); - inDart = true; - foundDart = true; + block.clear(); + } else if (trimmedLine == _dartDocPrefix) { + block.add(''); + } else { + final int index = line.indexOf(_dartDocPrefixWithSpace); + if (index < 0) { + throw SampleCheckerException( + 'Dart section inexplicably did not contain "$_dartDocPrefixWithSpace" prefix.', + file: relativeFilePath, + line: lineNumber, + ); + } + block.add(line.substring(index + 4)); } + } else if (_codeBlockStartRegex.hasMatch(trimmedLine)) { + assert(block.isEmpty); + startLine = Line( + '', + filename: relativeFilePath, + line: lineNumber + 1, + indent: line.indexOf(_dartDocPrefixWithSpace) + _dartDocPrefixWithSpace.length, + ); + inDart = true; } } if (!inSampleSection) { @@ -397,11 +429,7 @@ class SampleChecker { assert(block.isEmpty); startLine = Line('', filename: relativeFilePath, line: lineNumber + 1, indent: 3); inPreamble = true; - } else if (trimmedLine == '/// ## Sample code' || - trimmedLine.startsWith('/// ## Sample code:') || - trimmedLine == '/// ### Sample code' || - trimmedLine.startsWith('/// ### Sample code:') || - sampleMatch != null) { + } else if (sampleMatch != null) { inSnippet = sampleMatch != null ? sampleMatch[1] == 'snippet' : false; if (inSnippet) { startLine = Line( @@ -418,7 +446,12 @@ class SampleChecker { } } inSampleSection = !inSnippet; - foundDart = false; + } else if (RegExp(r'///\s*#+\s+[Ss]ample\s+[Cc]ode:?$').hasMatch(trimmedLine)) { + throw SampleCheckerException( + "Found deprecated '## Sample code' section: use {@tool sample}...{@end-tool} instead.", + file: relativeFilePath, + line: lineNumber, + ); } } } @@ -598,14 +631,17 @@ linter: ), ), ); - throw 'Cannot analyze dartdocs; analysis errors exist in ${file.path}: $error'; + throw SampleCheckerException( + 'Cannot analyze dartdocs; analysis errors exist: $error', + file: file.path, + line: lineNumber, + ); } if (errorCode == 'unused_element' || errorCode == 'unused_local_variable') { // We don't really care if sample code isn't used! continue; } - if (isSnippet) { addAnalysisError( file, @@ -630,14 +666,20 @@ linter: Line('', filename: file.path, line: lineNumber), ), ); - throw 'Failed to parse error message (read line number as $lineNumber; ' - 'total number of lines is ${fileContents.length}): $error'; + throw SampleCheckerException('Failed to parse error message: $error', file: file.path, line: lineNumber); } final Section actualSection = sections[file.path]; + if (actualSection == null) { + throw SampleCheckerException( + "Unknown section for ${file.path}. Maybe the temporary directory wasn't empty?", + file: file.path, + line: lineNumber, + ); + } final Line actualLine = actualSection.code[lineNumber - 1]; - if (actualLine.filename == null) { + if (actualLine?.filename == null) { if (errorCode == 'missing_identifier' && lineNumber > 1) { if (fileContents[lineNumber - 2].endsWith(',')) { final Line actualLine = sections[file.path].code[lineNumber - 2]; @@ -698,7 +740,7 @@ linter: /// into valid Dart code. Section _processBlock(Line line, List block) { if (block.isEmpty) { - throw '$line: Empty ```dart block in sample code.'; + throw SampleCheckerException('$line: Empty ```dart block in sample code.'); } if (block.first.startsWith('new ') || block.first.startsWith('const ') || block.first.startsWith(_constructorRegExp)) { _expressionId += 1; @@ -721,8 +763,8 @@ linter: // treated as a separate code block. if (block[index] == '' || block[index] == '// ...') { if (subline == null) - throw '${Line('', filename: line.filename, line: line.line + index, indent: line.indent)}: ' - 'Unexpected blank line or "// ..." line near start of subblock in sample code.'; + throw SampleCheckerException('${Line('', filename: line.filename, line: line.line + index, indent: line.indent)}: ' + 'Unexpected blank line or "// ..." line near start of subblock in sample code.'); subblocks += 1; subsections.add(_processBlock(subline, buffer)); buffer.clear(); diff --git a/dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart b/dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart index e624931f1e..6a00c15f9a 100644 --- a/dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart +++ b/dev/bots/test/analyze-sample-code-test-input/known_broken_documentation.dart @@ -18,8 +18,7 @@ /// blabla 0.0, the penzance blabla is blabla not blabla at all. Bla the blabla /// 1.0, the blabla is blabla blabla blabla an blabla blabla. /// -/// ### Sample code -/// +/// {@tool sample} /// Bla blabla blabla some [Text] when the `_blabla` blabla blabla is true, and /// blabla it when it is blabla: /// @@ -29,9 +28,9 @@ /// child: const Text('Poor wandering ones!'), /// ) /// ``` +/// {@end-tool} /// -/// ## Sample code -/// +/// {@tool sample} /// Bla blabla blabla some [Text] when the `_blabla` blabla blabla is true, and /// blabla finale blabla: /// @@ -41,3 +40,4 @@ /// child: const Text('Poor wandering ones!'), /// ) /// ``` +/// {@end-tool} diff --git a/dev/bots/test/analyze-sample-code_test.dart b/dev/bots/test/analyze-sample-code_test.dart index 8d3d883cc9..0d46585a6d 100644 --- a/dev/bots/test/analyze-sample-code_test.dart +++ b/dev/bots/test/analyze-sample-code_test.dart @@ -17,9 +17,9 @@ void main() { ..removeWhere((String line) => line.startsWith('Analyzer output:')); expect(process.exitCode, isNot(equals(0))); expect(stderrLines, [ - 'known_broken_documentation.dart:27:9: new Opacity(', + 'known_broken_documentation.dart:26:9: new Opacity(', '>>> Unnecessary new keyword (unnecessary_new)', - 'known_broken_documentation.dart:39:9: new Opacity(', + 'known_broken_documentation.dart:38:9: new Opacity(', '>>> Unnecessary new keyword (unnecessary_new)', '', 'Found 1 sample code errors.', diff --git a/dev/docs/assets/snippets.css b/dev/docs/assets/snippets.css index 65a7d338cb..4fb200addb 100644 --- a/dev/docs/assets/snippets.css +++ b/dev/docs/assets/snippets.css @@ -1,6 +1,6 @@ /* Styles for handling custom code snippets */ .snippet-container { - background-color: #45aae8; + background-color: #2372a3; padding: 10px; overflow: auto; } @@ -30,8 +30,21 @@ color: white; } +.snippet-description a:link { + color: #c7fcf4; +} +.snippet-description a:visited { + color: #c7dbfc; +} +.snippet-description a:hover { + color: white; +} +.snippet-description a:active { + color: #80b0fc; +} + .snippet-buttons button { - background-color: #45aae8; + background-color: #2372a3; border-style: none; color: white; padding: 10px 24px; @@ -82,7 +95,7 @@ height: 28px; width: 28px; transition: .3s ease; - background-color: #45aae8; + background-color: #2372a3; } .copy-button { @@ -102,7 +115,7 @@ .copy-image { opacity: 0.65; - color: #45aae8; + color: #2372a3; font-size: 28px; padding-top: 4px; } diff --git a/dev/docs/assets/snippets.js b/dev/docs/assets/snippets.js index b51c96e91c..9d4da921da 100644 --- a/dev/docs/assets/snippets.js +++ b/dev/docs/assets/snippets.js @@ -2,15 +2,10 @@ * Scripting for handling custom code snippets */ -const shortSnippet = 'shortSnippet'; -const longSnippet = 'longSnippet'; -var visibleSnippet = shortSnippet; - /** - * Shows the requested snippet. Values for "name" can be "shortSnippet" or - * "longSnippet". + * Shows the requested snippet, and stores the current state in visibleSnippet. */ -function showSnippet(name) { +function showSnippet(name, visibleSnippet) { if (visibleSnippet == name) return; if (visibleSnippet != null) { var shown = document.getElementById(visibleSnippet); @@ -39,6 +34,7 @@ function showSnippet(name) { if (button != null) { button.setAttributeNode(selectedAttribute); } + return visibleSnippet; } // Finds a sibling to given element with the given id. @@ -64,8 +60,8 @@ function supportsCopying() { // Copies the text inside the currently visible snippet to the clipboard, or the // given element, if any. function copyTextToClipboard(element) { - if (element == null) { - var elementSelector = '#' + visibleSnippet + ' .language-dart'; + if (typeof element === 'string') { + var elementSelector = '#' + element + ' .language-dart'; element = document.querySelector(elementSelector); if (element == null) { console.log( diff --git a/dev/snippets/README.md b/dev/snippets/README.md index 0606877c7b..a1e7875ade 100644 --- a/dev/snippets/README.md +++ b/dev/snippets/README.md @@ -7,8 +7,9 @@ snippets. This takes code in dartdocs, like this: ```dart -/// The following is a skeleton of a stateless widget subclass called `GreenFrog`: /// {@tool snippet --template="stateless_widget"} +/// The following is a skeleton of a stateless widget subclass called `GreenFrog`. +/// ```dart /// class GreenFrog extends StatelessWidget { /// const GreenFrog({ Key key }) : super(key: key); /// @@ -17,6 +18,7 @@ This takes code in dartdocs, like this: /// return Container(color: const Color(0xFF2DBD3A)); /// } /// } +/// ``` /// {@end-tool} ``` diff --git a/dev/snippets/config/skeletons/application.html b/dev/snippets/config/skeletons/application.html index afe9c4c3d5..7d7e17bab4 100644 --- a/dev/snippets/config/skeletons/application.html +++ b/dev/snippets/config/skeletons/application.html @@ -1,26 +1,30 @@ {@inject-html}
- - + + +
-
+
{{description}}
{{code}}
-