| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'package:args/command_runner.dart'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:watcher/watcher.dart'; | |
| import 'environment.dart'; | |
| import 'exceptions.dart'; | |
| import 'pipeline.dart'; | |
| import 'utils.dart'; | |
| const Map<String, String> targetAliases = <String, String>{ | |
| 'sdk': 'flutter/web_sdk', | |
| 'web_sdk': 'flutter/web_sdk', | |
| 'canvaskit': 'flutter/third_party/canvaskit:canvaskit_group', | |
| 'canvaskit_chromium': 'flutter/third_party/canvaskit:canvaskit_chromium_group', | |
| 'skwasm': 'flutter/third_party/canvaskit:skwasm_group', | |
| 'archive': 'flutter/web_sdk:flutter_web_sdk_archive', | |
| }; | |
| class BuildCommand extends Command<bool> with ArgUtils<bool> { | |
| BuildCommand() { | |
| argParser.addFlag( | |
| 'watch', | |
| abbr: 'w', | |
| help: 'Run the build in watch mode so it rebuilds whenever a change is ' | |
| 'made. Disabled by default.', | |
| ); | |
| argParser.addFlag( | |
| 'host', | |
| help: 'Build the host build instead of the wasm build, which is ' | |
| 'currently needed for `flutter run --local-engine` to work.' | |
| ); | |
| argParser.addFlag( | |
| 'profile', | |
| help: 'Build in profile mode instead of release mode. In this mode, the ' | |
| 'output will be located at "out/wasm_profile".\nThis only applies to ' | |
| 'the wasm build. The host build is always built in release mode.', | |
| ); | |
| argParser.addFlag( | |
| 'debug', | |
| help: 'Build in debug mode instead of release mode. In this mode, the ' | |
| 'output will be located at "out/wasm_debug".\nThis only applies to ' | |
| 'the wasm build. The host build is always built in release mode.', | |
| ); | |
| argParser.addFlag( | |
| 'dwarf', | |
| help: 'Embed DWARF debugging info into the output wasm modules. This is ' | |
| 'only valid in debug mode.', | |
| ); | |
| } | |
| @override | |
| String get name => 'build'; | |
| @override | |
| String get description => 'Build the Flutter web engine.'; | |
| bool get isWatchMode => boolArg('watch'); | |
| bool get host => boolArg('host'); | |
| List<String> get targets => argResults?.rest ?? <String>[]; | |
| bool get embedDwarf => boolArg('dwarf'); | |
| @override | |
| FutureOr<bool> run() async { | |
| if (embedDwarf && runtimeMode != RuntimeMode.debug) { | |
| throw ToolExit('Embedding DWARF data requires debug runtime mode.'); | |
| } | |
| final FilePath libPath = FilePath.fromWebUi('lib'); | |
| final List<PipelineStep> steps = <PipelineStep>[ | |
| GnPipelineStep( | |
| host: host, | |
| runtimeMode: runtimeMode, | |
| embedDwarf: embedDwarf, | |
| ), | |
| NinjaPipelineStep( | |
| host: host, | |
| runtimeMode: runtimeMode, | |
| targets: targets.map((String target) => targetAliases[target] ?? target), | |
| ), | |
| ]; | |
| final Pipeline buildPipeline = Pipeline(steps: steps); | |
| await buildPipeline.run(); | |
| if (isWatchMode) { | |
| print('Initial build done!'); | |
| print('Watching directory: ${libPath.relativeToCwd}/'); | |
| await PipelineWatcher( | |
| dir: libPath.absolute, | |
| pipeline: buildPipeline, | |
| // Ignore font files that are copied whenever tests run. | |
| ignore: (WatchEvent event) => event.path.endsWith('.ttf'), | |
| ).start(); | |
| } | |
| return true; | |
| } | |
| } | |
| /// Runs `gn`. | |
| /// | |
| /// Not safe to interrupt as it may leave the `out/` directory in a corrupted | |
| /// state. GN is pretty quick though, so it's OK to not support interruption. | |
| class GnPipelineStep extends ProcessStep { | |
| GnPipelineStep({ | |
| required this.host, | |
| required this.runtimeMode, | |
| required this.embedDwarf, | |
| }); | |
| final bool host; | |
| final RuntimeMode runtimeMode; | |
| final bool embedDwarf; | |
| @override | |
| String get description => 'gn'; | |
| @override | |
| bool get isSafeToInterrupt => false; | |
| List<String> get _gnArgs { | |
| if (host) { | |
| return <String>[ | |
| '--unoptimized', | |
| '--full-dart-sdk', | |
| ]; | |
| } else { | |
| return <String>[ | |
| '--web', | |
| '--runtime-mode=${runtimeMode.name}', | |
| if (runtimeMode == RuntimeMode.debug) | |
| '--unoptimized', | |
| if (embedDwarf) | |
| '--wasm-use-dwarf', | |
| ]; | |
| } | |
| } | |
| @override | |
| Future<ProcessManager> createProcess() { | |
| print('Running gn...'); | |
| return startProcess( | |
| path.join(environment.flutterDirectory.path, 'tools', 'gn'), | |
| _gnArgs, | |
| ); | |
| } | |
| } | |
| /// Runs `autoninja`. | |
| /// | |
| /// Can be safely interrupted. | |
| class NinjaPipelineStep extends ProcessStep { | |
| NinjaPipelineStep({ | |
| required this.host, | |
| required this.runtimeMode, | |
| required this.targets, | |
| }); | |
| @override | |
| String get description => 'ninja'; | |
| @override | |
| bool get isSafeToInterrupt => true; | |
| final bool host; | |
| final Iterable<String> targets; | |
| final RuntimeMode runtimeMode; | |
| String get buildDirectory { | |
| if (host) { | |
| return environment.hostDebugUnoptDir.path; | |
| } | |
| return getBuildDirectoryForRuntimeMode(runtimeMode).path; | |
| } | |
| @override | |
| Future<ProcessManager> createProcess() { | |
| print('Running autoninja...'); | |
| return startProcess( | |
| 'autoninja', | |
| <String>[ | |
| '-C', | |
| buildDirectory, | |
| ...targets, | |
| ], | |
| ); | |
| } | |
| } |