diff --git a/.cirrus.yml b/.cirrus.yml index 9b391299c89..5b86a63bd04 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -184,7 +184,15 @@ task: - bash <(curl -s https://codecov.io/bash) -c -f packages/flutter_tools/coverage/lcov.info -F flutter_tool - name: web_integration_tests - << : *WEB_SHARD_TEMPLATE + only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter/**', 'packages/flutter_test/**', 'packages/flutter_tools/**', 'packages/flutter_web_plugins/**', 'bin/internal/**') || $CIRRUS_PR == ''" + environment: + # As of October 2019, the Web shards needed more than 6G of RAM. + CPU: 2 + MEMORY: 8G + CHROME_NO_SANDBOX: true + GOLD_SERVICE_ACCOUNT: ENCRYPTED[3afeea5ac7201151c3d0dc9648862f0462b5e4f55dc600ca8b692319622f7c3eda3d577b1b16cc2ef0311b7314c1c095] + script: + - dart --enable-asserts ./dev/bots/test.dart - name: web_tests-0-linux << : *WEB_SHARD_TEMPLATE diff --git a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart index b401ec969d3..8e85481264b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart @@ -289,7 +289,7 @@ class IconTreeShaker { for (final Map iconDataMap in consts.constantInstances) { if ((iconDataMap['fontPackage'] ?? '') is! String || // Null is ok here. iconDataMap['fontFamily'] is! String || - iconDataMap['codePoint'] is! int) { + iconDataMap['codePoint'] is! num) { throw IconTreeShakerException._( 'Invalid ConstFinder result. Expected "fontPackage" to be a String, ' '"fontFamily" to be a String, and "codePoint" to be an int, ' @@ -301,7 +301,7 @@ class IconTreeShaker { ? family : 'packages/$package/$family'; result[key] ??= []; - result[key].add(iconDataMap['codePoint'] as int); + result[key].add((iconDataMap['codePoint'] as num).round()); } return result; } diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart index 79618de8e04..01c1c313acf 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/web.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart @@ -156,9 +156,26 @@ class Dart2JSTarget extends Target { final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); final String specPath = globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json'); final String packageFile = PackageMap.globalPackagesPath; + final File outputKernel = environment.buildDir.childFile('app.dill'); final File outputFile = environment.buildDir.childFile('main.dart.js'); + final List dartDefines = parseDartDefines(environment); - final ProcessResult result = await globals.processManager.run([ + // Run the dart2js compilation in two stages, so that icon tree shaking can + // parse the kernel file for web builds. + final ProcessResult kernelResult = await globals.processManager.run([ + globals.artifacts.getArtifactPath(Artifact.engineDartBinary), + globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), + '--libraries-spec=$specPath', + '-o', + outputKernel.path, + '--packages=$packageFile', + '--cfe-only', + environment.buildDir.childFile('main.dart').path, + ]); + if (kernelResult.exitCode != 0) { + throw Exception(kernelResult.stdout + kernelResult.stderr); + } + final ProcessResult javaScriptResult = await globals.processManager.run([ globals.artifacts.getArtifactPath(Artifact.engineDartBinary), globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot), '--libraries-spec=$specPath', @@ -166,26 +183,25 @@ class Dart2JSTarget extends Target { '-$dart2jsOptimization' else '-O4', - if (buildMode == BuildMode.profile) - '--no-minify', - '-o', - outputFile.path, - '--packages=$packageFile', if (buildMode == BuildMode.profile) '-Ddart.vm.profile=true' else '-Ddart.vm.product=true', + for (final String dartDefine in dartDefines) + '-D$dartDefine', + if (buildMode == BuildMode.profile) + '--no-minify', if (csp) '--csp', - for (final String dartDefine in parseDartDefines(environment)) - '-D$dartDefine', - environment.buildDir.childFile('main.dart').path, + '-o', + outputFile.path, + environment.buildDir.childFile('app.dill').path, ]); - if (result.exitCode != 0) { - throw Exception(result.stdout + result.stderr); + if (javaScriptResult.exitCode != 0) { + throw Exception(javaScriptResult.stdout + javaScriptResult.stderr); } final File dart2jsDeps = environment.buildDir - .childFile('main.dart.js.deps'); + .childFile('app.dill.deps'); if (!dart2jsDeps.existsSync()) { globals.printError('Warning: dart2js did not produced expected deps list at ' '${dart2jsDeps.path}'); @@ -197,7 +213,7 @@ class Dart2JSTarget extends Target { platform: globals.platform, ); final Depfile depfile = depfileService.parseDart2js( - environment.buildDir.childFile('main.dart.js.deps'), + environment.buildDir.childFile('app.dill.deps'), outputFile, ); depfileService.writeToFile( diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 15a1f6e05f8..6d8acc5c0a1 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -11,6 +11,7 @@ import '../base/logger.dart'; import '../build_info.dart'; import '../build_system/build_system.dart'; import '../build_system/targets/dart.dart'; +import '../build_system/targets/icon_tree_shaker.dart'; import '../build_system/targets/web.dart'; import '../convert.dart'; import '../globals.dart' as globals; @@ -52,8 +53,7 @@ Future buildWeb( kHasWebPlugins: hasWebPlugins.toString(), kDartDefines: jsonEncode(dartDefines), kCspMode: csp.toString(), - // TODO(dnfield): Enable font subset. We need to get a kernel file to do - // that. https://github.com/flutter/flutter/issues/49730 + kIconTreeShakerFlag: buildInfo.treeShakeIcons.toString(), }, )); if (!result.success) { diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index 3e8002b7d32..f20b2966be9 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -16,35 +16,38 @@ import 'package:process/process.dart'; import 'package:platform/platform.dart'; import '../../../src/common.dart'; +import '../../../src/context.dart'; import '../../../src/mocks.dart'; import '../../../src/testbed.dart'; +const List kDart2jsLinuxArgs = [ + 'bin/cache/dart-sdk/bin/dart', + 'bin/cache/dart-sdk/bin/snapshots/dart2js.dart.snapshot', + '--libraries-spec=bin/cache/flutter_web_sdk/libraries.json', +]; + void main() { Testbed testbed; Environment environment; - MockPlatform mockPlatform; - MockPlatform mockWindowsPlatform; + FakeProcessManager processManager; + final Platform linux = FakePlatform( + operatingSystem: 'linux', + environment: {}, + ); + final Platform windows = FakePlatform( + operatingSystem: 'windows', + environment: {}, + ); DepfileService depfileService; setUp(() { - mockPlatform = MockPlatform(); - mockWindowsPlatform = MockPlatform(); - - when(mockPlatform.isWindows).thenReturn(false); - when(mockPlatform.isMacOS).thenReturn(true); - when(mockPlatform.isLinux).thenReturn(false); - when(mockPlatform.environment).thenReturn(const {}); - - when(mockWindowsPlatform.isWindows).thenReturn(true); - when(mockWindowsPlatform.isMacOS).thenReturn(false); - when(mockWindowsPlatform.isLinux).thenReturn(false); - testbed = Testbed(setup: () { final File packagesFile = globals.fs.file(globals.fs.path.join('foo', '.packages')) ..createSync(recursive: true) ..writeAsStringSync('foo:lib/\n'); PackageMap.globalPackagesPath = packagesFile.path; globals.fs.currentDirectory.childDirectory('bar').createSync(); + processManager = FakeProcessManager.list([]); environment = Environment.test( globals.fs.currentDirectory, @@ -61,7 +64,7 @@ void main() { ); environment.buildDir.createSync(recursive: true); }, overrides: { - Platform: () => mockPlatform, + Platform: () => linux, }); }); @@ -156,7 +159,7 @@ void main() { // Import. expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); }, overrides: { - Platform: () => mockWindowsPlatform, + Platform: () => windows, })); test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async { @@ -216,111 +219,131 @@ void main() { test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[kCspMode] = 'true'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O4', + '-Ddart.vm.profile=true', + '--no-minify', + '--csp', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O4', // highest optimizations - '--no-minify', // but uses unminified names for debugging - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.profile=true', - '--csp', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O4', + '-Ddart.vm.profile=true', + '--no-minify', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O4', // highest optimizations - '--no-minify', // but uses unminified names for debugging - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.profile=true', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in release mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O4', + '-Ddart.vm.product=true', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O4', // highest optimizations. - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.product=true', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kDart2jsOptimization] = 'O3'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O3', + '-Ddart.vm.product=true', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O3', // configured optimizations. - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.product=true', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget produces expected depfile', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - environment.buildDir.childFile('main.dart.js.deps') + environment.buildDir.childFile('app.dill.deps') .writeAsStringSync('file:///a.dart'); return FakeProcessResult(exitCode: 0); }); await const Dart2JSTarget().build(environment); - expect(environment.buildDir.childFile('dart2js.d').existsSync(), true); + expect(environment.buildDir.childFile('dart2js.d'), exists); final Depfile depfile = depfileService.parse(environment.buildDir.childFile('dart2js.d')); expect(depfile.inputs.single.path, globals.fs.path.absolute('a.dart')); @@ -333,54 +356,64 @@ void main() { test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O4', + '-Ddart.vm.product=true', + '-DFOO=bar', + '-DBAZ=qux', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O4', - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.product=true', - '-DFOO=bar', - '-DBAZ=qux', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[kDartDefines] = '["FOO=bar","BAZ=qux"]'; - when(globals.processManager.run(any)).thenAnswer((Invocation invocation) async { - return FakeProcessResult(exitCode: 0); - }); - await const Dart2JSTarget().build(environment); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-o', + environment.buildDir.childFile('app.dill').absolute.path, + '--packages=${globals.fs.path.join('foo', '.packages')}', + '--cfe-only', + environment.buildDir.childFile('main.dart').absolute.path, + ] + )); + processManager.addCommand(FakeCommand( + command: [ + ...kDart2jsLinuxArgs, + '-O4', + '-Ddart.vm.profile=true', + '-DFOO=bar', + '-DBAZ=qux', + '--no-minify', + '-o', + environment.buildDir.childFile('main.dart.js').absolute.path, + environment.buildDir.childFile('app.dill').absolute.path, + ] + )); - final List expected = [ - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'), - globals.fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'), - '--libraries-spec=' + globals.fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'), - '-O4', - '--no-minify', - '-o', - environment.buildDir.childFile('main.dart.js').absolute.path, - '--packages=${globals.fs.path.join('foo', '.packages')}', - '-Ddart.vm.profile=true', - '-DFOO=bar', - '-DBAZ=qux', - environment.buildDir.childFile('main.dart').absolute.path, - ]; - verify(globals.processManager.run(expected)).called(1); + await const Dart2JSTarget().build(environment); }, overrides: { - ProcessManager: () => MockProcessManager(), + ProcessManager: () => processManager, })); test('Dart2JSTarget throws developer-friendly exception on misformatted DartDefines', () => testbed.run(() async { @@ -427,4 +460,3 @@ void main() { } class MockProcessManager extends Mock implements ProcessManager {} -class MockPlatform extends Mock implements Platform {}