Reland: Switch to assemble API for dart2js (#41659)
* Stop using build_runner for dart2js
* fixes to yield when computing hashes and to imports
* add missing await
* Update filecache_test.dart
* Fix paths in filecache test
* use file uri for import
* add test cases and configurable override
* remove test dep
* fix filepaths for windows
* test no longer failing
* fix paths for test cases
* fix typo
* address comments
* make a constant
* make filehash async and use constant
* fix silly logic error
diff --git a/packages/flutter_tools/lib/src/build_runner/build_script.dart b/packages/flutter_tools/lib/src/build_runner/build_script.dart
index 0638b8f..69d0569 100644
--- a/packages/flutter_tools/lib/src/build_runner/build_script.dart
+++ b/packages/flutter_tools/lib/src/build_runner/build_script.dart
@@ -4,21 +4,17 @@
// ignore_for_file: implementation_imports
import 'dart:async';
-import 'dart:convert'; // ignore: dart_convert_import
-import 'dart:io'; // ignore: dart_io_import
import 'dart:isolate';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
-import 'package:archive/archive.dart';
import 'package:build/build.dart';
import 'package:build_config/build_config.dart';
import 'package:build_modules/build_modules.dart';
import 'package:build_modules/builders.dart';
import 'package:build_modules/src/module_builder.dart';
import 'package:build_modules/src/platform.dart';
-import 'package:build_modules/src/workers.dart';
import 'package:build_runner/build_runner.dart' as build_runner;
import 'package:build_runner_core/build_runner_core.dart' as core;
import 'package:build_test/builder.dart';
@@ -26,10 +22,7 @@
import 'package:build_web_compilers/build_web_compilers.dart';
import 'package:build_web_compilers/builders.dart';
import 'package:build_web_compilers/src/dev_compiler_bootstrap.dart';
-import 'package:crypto/crypto.dart';
-import 'package:glob/glob.dart';
import 'package:path/path.dart' as path; // ignore: package_path_import
-import 'package:scratch_space/scratch_space.dart';
import 'package:test_core/backend.dart';
const String ddcBootstrapExtension = '.dart.bootstrap.js';
@@ -248,12 +241,8 @@
@override
Future<void> build(BuildStep buildStep) async {
- if (release || profile) {
- await bootstrapDart2Js(buildStep, flutterWebSdk, profile);
- } else {
- await bootstrapDdc(buildStep, platform: flutterWebPlatform,
- skipPlatformCheckPackages: skipPlatformCheckPackages);
- }
+ await bootstrapDdc(buildStep, platform: flutterWebPlatform,
+ skipPlatformCheckPackages: skipPlatformCheckPackages);
}
}
@@ -422,116 +411,6 @@
};
}
-Future<void> bootstrapDart2Js(BuildStep buildStep, String flutterWebSdk, bool profile) async {
- final AssetId dartEntrypointId = buildStep.inputId;
- final AssetId moduleId = dartEntrypointId.changeExtension(moduleExtension(flutterWebPlatform));
- final Module module = Module.fromJson(json.decode(await buildStep.readAsString(moduleId)));
- final List<Module> allDeps = await module.computeTransitiveDependencies(
- buildStep,
- throwIfUnsupported: true,
- skipPlatformCheckPackages: skipPlatformCheckPackages,
- )..add(module);
- final ScratchSpace scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
- final Iterable<AssetId> allSrcs = allDeps.expand((Module module) => module.sources);
- await scratchSpace.ensureAssets(allSrcs, buildStep);
-
- final String packageFile = _createPackageFile(allSrcs, buildStep, scratchSpace);
- final String dartPath = dartEntrypointId.path.startsWith('lib/')
- ? 'package:${dartEntrypointId.package}/'
- '${dartEntrypointId.path.substring('lib/'.length)}'
- : dartEntrypointId.path;
- final String jsOutputPath =
- '${path.withoutExtension(dartPath.replaceFirst('package:', 'packages/'))}'
- '$jsEntrypointExtension';
- final String flutterWebSdkPath = flutterWebSdk;
- final String librariesPath = path.join(flutterWebSdkPath, 'libraries.json');
- final List<String> args = <String>[
- '--libraries-spec="$librariesPath"',
- if (profile)
- '-O1'
- else
- '-O4',
- '-o',
- '$jsOutputPath',
- '--packages="$packageFile"',
- if (profile)
- '-Ddart.vm.profile=true'
- else
- '-Ddart.vm.product=true',
- dartPath,
- ];
- final Dart2JsBatchWorkerPool dart2js = await buildStep.fetchResource(dart2JsWorkerResource);
- final Dart2JsResult result = await dart2js.compile(args);
- final AssetId jsOutputId = dartEntrypointId.changeExtension(jsEntrypointExtension);
- final File jsOutputFile = scratchSpace.fileFor(jsOutputId);
- if (result.succeeded && jsOutputFile.existsSync()) {
- final String rootDir = path.dirname(jsOutputFile.path);
- final String dartFile = path.basename(dartEntrypointId.path);
- final Glob fileGlob = Glob('$dartFile.js*');
- final Archive archive = Archive();
- await for (FileSystemEntity jsFile in fileGlob.list(root: rootDir)) {
- if (jsFile.path.endsWith(jsEntrypointExtension) ||
- jsFile.path.endsWith(jsEntrypointSourceMapExtension)) {
- // These are explicitly output, and are not part of the archive.
- continue;
- }
- if (jsFile is File) {
- final String fileName = path.relative(jsFile.path, from: rootDir);
- final FileStat fileStats = jsFile.statSync();
- archive.addFile(
- ArchiveFile(fileName, fileStats.size, await jsFile.readAsBytes())
- ..mode = fileStats.mode
- ..lastModTime = fileStats.modified.millisecondsSinceEpoch);
- }
- }
- if (archive.isNotEmpty) {
- final AssetId archiveId = dartEntrypointId.changeExtension(jsEntrypointArchiveExtension);
- await buildStep.writeAsBytes(archiveId, TarEncoder().encode(archive));
- }
-
- // Explicitly write out the original js file and sourcemap - we can't output
- // these as part of the archive because they already have asset nodes.
- await scratchSpace.copyOutput(jsOutputId, buildStep);
- final AssetId jsSourceMapId =
- dartEntrypointId.changeExtension(jsEntrypointSourceMapExtension);
- await _copyIfExists(jsSourceMapId, scratchSpace, buildStep);
- } else {
- log.severe(result.output);
- }
-}
-
-Future<void> _copyIfExists(
- AssetId id, ScratchSpace scratchSpace, AssetWriter writer) async {
- final File file = scratchSpace.fileFor(id);
- if (file.existsSync()) {
- await scratchSpace.copyOutput(id, writer);
- }
-}
-
-/// Creates a `.packages` file unique to this entrypoint at the root of the
-/// scratch space and returns it's filename.
-///
-/// Since mulitple invocations of Dart2Js will share a scratch space and we only
-/// know the set of packages involved the current entrypoint we can't construct
-/// a `.packages` file that will work for all invocations of Dart2Js so a unique
-/// file is created for every entrypoint that is run.
-///
-/// The filename is based off the MD5 hash of the asset path so that files are
-/// unique regarless of situations like `web/foo/bar.dart` vs
-/// `web/foo-bar.dart`.
-String _createPackageFile(Iterable<AssetId> inputSources, BuildStep buildStep, ScratchSpace scratchSpace) {
- final Uri inputUri = buildStep.inputId.uri;
- final String packageFileName =
- '.package-${md5.convert(inputUri.toString().codeUnits)}';
- final File packagesFile =
- scratchSpace.fileFor(AssetId(buildStep.inputId.package, packageFileName));
- final Set<String> packageNames = inputSources.map((AssetId s) => s.package).toSet();
- final String packagesFileContent =
- packageNames.map((String name) => '$name:packages/$name/').join('\n');
- packagesFile .writeAsStringSync('# Generated for $inputUri\n$packagesFileContent');
- return packageFileName;
-}
-
/// Returns whether or not [dartId] is an app entrypoint (basically, whether
/// or not it has a `main` function).
Future<bool> _isAppEntryPoint(AssetId dartId, AssetReader reader) async {
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index 2ed586b..f4d1f94 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -6,6 +6,7 @@
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vmservice;
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
import '../application_package.dart';
import '../base/common.dart';
@@ -20,6 +21,7 @@
import '../project.dart';
import '../reporting/reporting.dart';
import '../resident_runner.dart';
+import '../web/chrome.dart';
import '../web/web_device.dart';
import '../web/web_runner.dart';
import 'web_fs.dart';
@@ -170,7 +172,7 @@
initializePlatform: debuggingOptions.initializePlatform,
hostname: debuggingOptions.hostname,
port: debuggingOptions.port,
- skipDwds: device is WebServerDevice,
+ skipDwds: device is WebServerDevice || !debuggingOptions.buildInfo.isDebug,
);
// When connecting to a browser, update the message with a seemsSlow notification
// to handle the case where we fail to connect.
@@ -291,6 +293,21 @@
).send();
}
}
+ // Allows browser refresh hot restart on non-debug builds.
+ if (device is ChromeDevice && debuggingOptions.browserLaunch) {
+ try {
+ final Chrome chrome = await ChromeLauncher.connectedInstance;
+ final ChromeTab chromeTab = await chrome.chromeConnection.getTab((ChromeTab chromeTab) {
+ return chromeTab.url.contains(debuggingOptions.hostname);
+ });
+ final WipConnection wipConnection = await chromeTab.connect();
+ await wipConnection.sendCommand('Page.reload');
+ status.stop();
+ return OperationResult.ok;
+ } catch (err) {
+ // Ignore error and continue with posted message;
+ }
+ }
status.stop();
printStatus('Recompile complete. Page requires refresh.');
return OperationResult.ok;
diff --git a/packages/flutter_tools/lib/src/build_runner/web_fs.dart b/packages/flutter_tools/lib/src/build_runner/web_fs.dart
index 456cf07..c2cca4d 100644
--- a/packages/flutter_tools/lib/src/build_runner/web_fs.dart
+++ b/packages/flutter_tools/lib/src/build_runner/web_fs.dart
@@ -35,6 +35,7 @@
import '../plugins.dart';
import '../project.dart';
import '../web/chrome.dart';
+import '../web/compile.dart';
/// The name of the built web project.
const String kBuildTargetName = 'web';
@@ -89,6 +90,11 @@
this._dwds,
this.uri,
this._assetServer,
+ this._useBuildRunner,
+ this._flutterProject,
+ this._target,
+ this._buildInfo,
+ this._initializePlatform,
);
/// The server uri.
@@ -98,12 +104,17 @@
final Dwds _dwds;
final BuildDaemonClient _client;
final AssetServer _assetServer;
+ final bool _useBuildRunner;
+ final FlutterProject _flutterProject;
+ final String _target;
+ final BuildInfo _buildInfo;
+ final bool _initializePlatform;
StreamSubscription<void> _connectedApps;
static const String _kHostName = 'localhost';
Future<void> stop() async {
- await _client.close();
+ await _client?.close();
await _dwds?.stop();
await _server.close(force: true);
await _connectedApps?.cancel();
@@ -132,6 +143,10 @@
/// Recompile the web application and return whether this was successful.
Future<bool> recompile() async {
+ if (!_useBuildRunner) {
+ await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform);
+ return true;
+ }
_client.startBuild();
await for (BuildResults results in _client.buildResults) {
final BuildResult result = results.results.firstWhere((BuildResult result) {
@@ -161,39 +176,7 @@
if (!flutterProject.dartTool.existsSync()) {
flutterProject.dartTool.createSync(recursive: true);
}
- final bool hasWebPlugins = findPlugins(flutterProject)
- .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
- // Start the build daemon and run an initial build.
- final Completer<bool> inititalBuild = Completer<bool>();
- final BuildDaemonClient client = await buildDaemonCreator
- .startBuildDaemon(fs.currentDirectory.path,
- release: buildInfo.isRelease,
- profile: buildInfo.isProfile,
- hasPlugins: hasWebPlugins,
- initializePlatform: initializePlatform,
- );
- client.startBuild();
- // Only provide relevant build results
- final Stream<BuildResult> filteredBuildResults = client.buildResults
- .asyncMap<BuildResult>((BuildResults results) {
- return results.results
- .firstWhere((BuildResult result) => result.target == kBuildTargetName);
- });
- final StreamSubscription<void> firstBuild = client.buildResults.listen((BuildResults buildResults) {
- if (inititalBuild.isCompleted) {
- return;
- }
- final BuildResult result = buildResults.results.firstWhere((BuildResult result) {
- return result.target == kBuildTargetName;
- });
- if (result.status == BuildStatus.failed) {
- inititalBuild.complete(false);
- }
- if (result.status == BuildStatus.succeeded) {
- inititalBuild.complete(true);
- }
- });
- final int daemonAssetPort = buildDaemonCreator.assetServerPort(fs.currentDirectory);
+ final Completer<bool> firstBuildCompleter = Completer<bool>();
// Initialize the asset bundle.
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
@@ -201,7 +184,7 @@
await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
final String targetBaseName = fs.path
- .withoutExtension(target).replaceFirst('lib${fs.path.separator}', '');
+ .withoutExtension(target).replaceFirst('lib${fs.path.separator}', '');
final Map<String, String> mappedUrls = <String, String>{
'main.dart.js': 'packages/${flutterProject.manifest.appName}/'
'${targetBaseName}_web_entrypoint.dart.js',
@@ -236,29 +219,78 @@
}
};
});
+
Handler handler;
Dwds dwds;
- if (!skipDwds) {
- dwds = await dwdsFactory(
- hostname: hostname ?? _kHostName,
- applicationPort: hostPort,
- applicationTarget: kBuildTargetName,
- assetServerPort: daemonAssetPort,
- buildResults: filteredBuildResults,
- chromeConnection: () async {
- return (await ChromeLauncher.connectedInstance).chromeConnection;
- },
- reloadConfiguration: ReloadConfiguration.none,
- serveDevTools: true,
- verbose: false,
- enableDebugExtension: true,
- logWriter: (dynamic level, String message) => printTrace(message),
- );
- handler = pipeline.addHandler(dwds.handler);
+ BuildDaemonClient client;
+ StreamSubscription<void> firstBuild;
+ if (buildInfo.isDebug) {
+ final bool hasWebPlugins = findPlugins(flutterProject)
+ .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
+ // Start the build daemon and run an initial build.
+ client = await buildDaemonCreator
+ .startBuildDaemon(fs.currentDirectory.path,
+ release: buildInfo.isRelease,
+ profile: buildInfo.isProfile,
+ hasPlugins: hasWebPlugins,
+ initializePlatform: initializePlatform,
+ );
+ client.startBuild();
+ // Only provide relevant build results
+ final Stream<BuildResult> filteredBuildResults = client.buildResults
+ .asyncMap<BuildResult>((BuildResults results) {
+ return results.results
+ .firstWhere((BuildResult result) => result.target == kBuildTargetName);
+ });
+ // Start the build daemon and run an initial build.
+ firstBuild = client.buildResults.listen((BuildResults buildResults) {
+ if (firstBuildCompleter.isCompleted) {
+ return;
+ }
+ final BuildResult result = buildResults.results.firstWhere((BuildResult result) {
+ return result.target == kBuildTargetName;
+ });
+ if (result.status == BuildStatus.failed) {
+ firstBuildCompleter.complete(false);
+ }
+ if (result.status == BuildStatus.succeeded) {
+ firstBuildCompleter.complete(true);
+ }
+ });
+ final int daemonAssetPort = buildDaemonCreator.assetServerPort(fs.currentDirectory);
+
+ // Initialize the asset bundle.
+ final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+ await assetBundle.build();
+ await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries);
+ if (!skipDwds) {
+ dwds = await dwdsFactory(
+ hostname: hostname ?? _kHostName,
+ applicationPort: hostPort,
+ applicationTarget: kBuildTargetName,
+ assetServerPort: daemonAssetPort,
+ buildResults: filteredBuildResults,
+ chromeConnection: () async {
+ return (await ChromeLauncher.connectedInstance).chromeConnection;
+ },
+ reloadConfiguration: ReloadConfiguration.none,
+ serveDevTools: true,
+ verbose: false,
+ enableDebugExtension: true,
+ logWriter: (dynamic level, String message) => printTrace(message),
+ );
+ handler = pipeline.addHandler(dwds.handler);
+ } else {
+ handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/'));
+ }
} else {
- handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/'));
+ await buildWeb(flutterProject, target, buildInfo, initializePlatform);
+ firstBuildCompleter.complete(true);
}
- final AssetServer assetServer = AssetServer(flutterProject, targetBaseName);
+
+ final AssetServer assetServer = buildInfo.isDebug
+ ? DebugAssetServer(flutterProject, targetBaseName)
+ : ReleaseAssetServer();
Cascade cascade = Cascade();
cascade = cascade.add(handler);
cascade = cascade.add(assetServer.handle);
@@ -270,23 +302,65 @@
dwds,
'http://$_kHostName:$hostPort/',
assetServer,
+ buildInfo.isDebug,
+ flutterProject,
+ target,
+ buildInfo,
+ initializePlatform,
);
- if (!await inititalBuild.future) {
+ if (!await firstBuildCompleter.future) {
throw Exception('Failed to compile for the web.');
}
- await firstBuild.cancel();
+ await firstBuild?.cancel();
return webFS;
}
}
-class AssetServer {
- AssetServer(this.flutterProject, this.targetBaseName);
+abstract class AssetServer {
+ Future<Response> handle(Request request);
+
+ void dispose() {}
+}
+
+class ReleaseAssetServer extends AssetServer {
+ @override
+ Future<Response> handle(Request request) async {
+ final Uri artifactUri = fs.directory(getWebBuildDirectory()).uri.resolveUri(request.url);
+ final File file = fs.file(artifactUri);
+ if (file.existsSync()) {
+ return Response.ok(file.readAsBytesSync(), headers: <String, String>{
+ 'Content-Type': _guessExtension(file),
+ });
+ }
+ if (request.url.path == '') {
+ final File file = fs.file(fs.path.join(getWebBuildDirectory(), 'index.html'));
+ return Response.ok(file.readAsBytesSync(), headers: <String, String>{
+ 'Content-Type': _guessExtension(file),
+ });
+ }
+ return Response.notFound('');
+ }
+
+ String _guessExtension(File file) {
+ switch (fs.path.extension(file.path)) {
+ case '.js':
+ return 'text/javascript';
+ case '.html':
+ return 'text/html';
+ }
+ return 'text';
+ }
+}
+
+class DebugAssetServer extends AssetServer {
+ DebugAssetServer(this.flutterProject, this.targetBaseName);
final FlutterProject flutterProject;
final String targetBaseName;
final PackageMap packageMap = PackageMap(PackageMap.globalPackagesPath);
Directory partFiles;
+ @override
Future<Response> handle(Request request) async {
if (request.url.path.endsWith('.html')) {
final Uri htmlUri = flutterProject.web.directory.uri.resolveUri(request.url);
@@ -411,6 +485,7 @@
return Response.notFound('');
}
+ @override
void dispose() {
partFiles?.deleteSync(recursive: true);
}
diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart
index b1efeff..e2bdbb8 100644
--- a/packages/flutter_tools/lib/src/build_system/build_system.dart
+++ b/packages/flutter_tools/lib/src/build_system/build_system.dart
@@ -25,6 +25,13 @@
/// The [BuildSystem] instance.
BuildSystem get buildSystem => context.get<BuildSystem>();
+/// A reasonable amount of files to open at the same time.
+///
+/// This number is somewhat arbitrary - it is difficult to detect whether
+/// or not we'll run out of file descriptiors when using async dart:io
+/// APIs.
+const int kMaxOpenFiles = 64;
+
/// Configuration for the build system itself.
class BuildSystemConfig {
/// Create a new [BuildSystemConfig].
diff --git a/packages/flutter_tools/lib/src/build_system/file_hash_store.dart b/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
index b43c311..afce327 100644
--- a/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
+++ b/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
@@ -7,6 +7,7 @@
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
+import 'package:pool/pool.dart';
import '../base/file_system.dart';
import '../convert.dart';
@@ -69,13 +70,6 @@
///
/// The format of the file store is subject to change and not part of its API.
///
-/// To regenerate the protobuf entries used to construct the cache:
-/// 1. If not already installed, https://developers.google.com/protocol-buffers/docs/downloads
-/// 2. pub global active `protoc-gen-dart`
-/// 3. protoc -I=lib/src/build_system/ --dart_out=lib/src/build_system/ lib/src/build_system/filecache.proto
-/// 4. Add licenses headers to the newly generated file and check-in.
-///
-/// See also: https://developers.google.com/protocol-buffers/docs/darttutorial
// TODO(jonahwilliams): find a better way to clear out old entries, perhaps
// track the last access or modification date?
class FileHashStore {
@@ -141,24 +135,28 @@
/// Computes a hash of the provided files and returns a list of entities
/// that were dirty.
- // TODO(jonahwilliams): compare hash performance with md5 tool on macOS and
- // linux and certutil on Windows, as well as dividing up computation across
- // isolates. This also related to the current performance issue with checking
- // APKs before installing them on device.
Future<List<File>> hashFiles(List<File> files) async {
final List<File> dirty = <File>[];
- for (File file in files) {
- final String absolutePath = file.resolveSymbolicLinksSync();
- final String previousHash = previousHashes[absolutePath];
- final List<int> bytes = file.readAsBytesSync();
- final String currentHash = md5.convert(bytes).toString();
+ final Pool openFiles = Pool(kMaxOpenFiles);
+ await Future.wait(<Future<void>>[
+ for (File file in files) _hashFile(file, dirty, openFiles)]);
+ return dirty;
+ }
+ Future<void> _hashFile(File file, List<File> dirty, Pool pool) async {
+ final PoolResource resource = await pool.request();
+ try {
+ final String absolutePath = file.path;
+ final String previousHash = previousHashes[absolutePath];
+ final Digest digest = md5.convert(await file.readAsBytes());
+ final String currentHash = digest.toString();
if (currentHash != previousHash) {
dirty.add(file);
}
currentHashes[absolutePath] = currentHash;
+ } finally {
+ resource.release();
}
- return dirty;
}
File get _cacheFile => environment.buildDir.childFile(_kFileCache);
diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
index 127d432..f783237 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
@@ -50,7 +50,9 @@
/// A specific asset behavior for building bundles.
class AssetOutputBehavior extends SourceBehavior {
- const AssetOutputBehavior();
+ const AssetOutputBehavior([this._pathSuffix = '']);
+
+ final String _pathSuffix;
@override
List<File> inputs(Environment environment) {
@@ -64,7 +66,7 @@
final List<File> results = <File>[];
final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
for (DevFSFileContent devFsContent in files) {
- results.add(fs.file(devFsContent.file.path));
+ results.add(fs.file(fs.path.join(_pathSuffix, devFsContent.file.path)));
}
return results;
}
@@ -78,7 +80,7 @@
);
final List<File> results = <File>[];
for (String key in assetBundle.entries.keys) {
- final File file = fs.file(fs.path.join(environment.outputDir.path, key));
+ final File file = fs.file(fs.path.join(environment.outputDir.path, _pathSuffix, key));
results.add(file);
}
return results;
@@ -125,7 +127,7 @@
packagesPath: environment.projectDir.childFile('.packages').path,
);
// Limit number of open files to avoid running out of file descriptors.
- final Pool pool = Pool(64);
+ final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index 92474cd..02a4021 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -115,23 +115,7 @@
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
await assetBundle.build();
- final Pool pool = Pool(64);
- await Future.wait<void>(
- assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
- final PoolResource resource = await pool.request();
- try {
- final File file = fs.file(fs.path.join(environment.outputDir.path, entry.key));
- file.parent.createSync(recursive: true);
- final DevFSContent content = entry.value;
- if (content is DevFSFileContent && content.file is File) {
- await (content.file as File).copy(file.path);
- } else {
- await file.writeAsBytes(await entry.value.contentsAsBytes());
- }
- } finally {
- resource.release();
- }
- }));
+ await copyAssets(assetBundle, environment);
}
@override
@@ -140,6 +124,28 @@
];
}
+/// A helper function to copy an [assetBundle] into an [environment]'s output directory,
+/// plus an optional [pathSuffix]
+Future<void> copyAssets(AssetBundle assetBundle, Environment environment, [String pathSuffix = '']) async {
+ final Pool pool = Pool(kMaxOpenFiles);
+ await Future.wait<void>(
+ assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
+ final PoolResource resource = await pool.request();
+ try {
+ final File file = fs.file(fs.path.join(environment.outputDir.path, pathSuffix, entry.key));
+ file.parent.createSync(recursive: true);
+ final DevFSContent content = entry.value;
+ if (content is DevFSFileContent && content.file is File) {
+ await (content.file as File).copy(file.path);
+ } else {
+ await file.writeAsBytes(await entry.value.contentsAsBytes());
+ }
+ } finally {
+ resource.release();
+ }
+ }));
+}
+
/// Copies the prebuilt flutter bundle for release mode.
class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
const ReleaseCopyFlutterBundle();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
index b05b3b8..a448b5b 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
@@ -336,7 +336,7 @@
}
// Limit number of open files to avoid running out of file descriptors.
try {
- final Pool pool = Pool(64);
+ final Pool pool = Pool(kMaxOpenFiles);
await Future.wait<void>(
assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
new file mode 100644
index 0000000..d097dcb
--- /dev/null
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -0,0 +1,203 @@
+// 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 '../../artifacts.dart';
+import '../../asset.dart';
+import '../../base/file_system.dart';
+import '../../base/io.dart';
+import '../../base/process_manager.dart';
+import '../../build_info.dart';
+import '../../dart/package_map.dart';
+import '../../globals.dart';
+import '../../project.dart';
+import '../build_system.dart';
+import 'assets.dart';
+import 'dart.dart';
+
+/// Whether web builds should call the platform initialization logic.
+const String kInitializePlatform = 'InitializePlatform';
+
+/// Whether the application has web plugins.
+const String kHasWebPlugins = 'HasWebPlugins';
+
+/// An override for the dart2js build mode.
+///
+/// Valid values are O1 (lowest, profile default) to O4 (highest, release default).
+const String kDart2jsOptimization = 'Dart2jsOptimization';
+
+/// Generates an entrypoint for a web target.
+class WebEntrypointTarget extends Target {
+ const WebEntrypointTarget();
+
+ @override
+ String get name => 'web_entrypoint';
+
+ @override
+ List<Target> get dependencies => const <Target>[];
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'),
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{BUILD_DIR}/main.dart'),
+ ];
+
+ @override
+ Future<void> build(Environment environment) async {
+ final String targetFile = environment.defines[kTargetFile];
+ final bool shouldInitializePlatform = environment.defines[kInitializePlatform] == 'true';
+ final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true';
+ final String import = fs.file(fs.path.absolute(targetFile)).uri.toString();
+
+ String contents;
+ if (hasPlugins) {
+ contents = '''
+import 'dart:ui' as ui;
+
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+
+import 'generated_plugin_registrant.dart';
+import "$import" as entrypoint;
+
+Future<void> main() async {
+ registerPlugins(webPluginRegistry);
+ if ($shouldInitializePlatform) {
+ await ui.webOnlyInitializePlatform();
+ }
+ entrypoint.main();
+}
+''';
+ } else {
+ contents = '''
+import 'dart:ui' as ui;
+
+import "$import" as entrypoint;
+
+Future<void> main() async {
+ if ($shouldInitializePlatform) {
+ await ui.webOnlyInitializePlatform();
+ }
+ entrypoint.main();
+}
+''';
+ }
+ environment.buildDir.childFile('main.dart')
+ ..writeAsStringSync(contents);
+ }
+}
+
+/// Compiles a web entrypoint with dart2js.
+class Dart2JSTarget extends Target {
+ const Dart2JSTarget();
+
+ @override
+ String get name => 'dart2js';
+
+ @override
+ List<Target> get dependencies => const <Target>[
+ WebEntrypointTarget()
+ ];
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'),
+ Source.artifact(Artifact.flutterWebSdk),
+ Source.artifact(Artifact.dart2jsSnapshot),
+ Source.artifact(Artifact.engineDartBinary),
+ Source.artifact(Artifact.engineDartSdkPath),
+ Source.pattern('{BUILD_DIR}/main.dart'),
+ Source.pattern('{PROJECT_DIR}/.packages'),
+ Source.function(listDartSources), // <- every dart file under {PROJECT_DIR}/lib and in .packages
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{BUILD_DIR}/main.dart.js'),
+ ];
+
+ @override
+ Future<void> build(Environment environment) async {
+ final String dart2jsOptimization = environment.defines[kDart2jsOptimization];
+ final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
+ final String specPath = fs.path.join(artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json');
+ final String packageFile = FlutterProject.fromDirectory(environment.projectDir).hasBuilders
+ ? PackageMap.globalGeneratedPackagesPath
+ : PackageMap.globalPackagesPath;
+ final ProcessResult result = await processManager.run(<String>[
+ artifacts.getArtifactPath(Artifact.engineDartBinary),
+ artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
+ '--libraries-spec=$specPath',
+ if (dart2jsOptimization != null)
+ '-$dart2jsOptimization'
+ else if (buildMode == BuildMode.profile)
+ '-O1'
+ else
+ '-O4',
+ '-o',
+ environment.buildDir.childFile('main.dart.js').path,
+ '--packages=$packageFile',
+ if (buildMode == BuildMode.profile)
+ '-Ddart.vm.profile=true'
+ else
+ '-Ddart.vm.product=true',
+ environment.buildDir.childFile('main.dart').path,
+ ]);
+ if (result.exitCode != 0) {
+ throw Exception(result.stdout + result.stderr);
+ }
+ }
+}
+
+/// Unpacks the dart2js compilation to a given output directory
+class WebReleaseBundle extends Target {
+ const WebReleaseBundle();
+
+ @override
+ String get name => 'web_release_bundle';
+
+ @override
+ List<Target> get dependencies => const <Target>[
+ Dart2JSTarget(),
+ ];
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.pattern('{BUILD_DIR}/main.dart.js'),
+ Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'),
+ Source.behavior(AssetOutputBehavior('assets')),
+ Source.pattern('{PROJECT_DIR}/web/index.html'),
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{OUTPUT_DIR}/main.dart.js'),
+ Source.pattern('{OUTPUT_DIR}/assets/AssetManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/assets/FontManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/assets/LICENSE'),
+ Source.pattern('{OUTPUT_DIR}/index.html'),
+ Source.behavior(AssetOutputBehavior('assets'))
+ ];
+
+ @override
+ Future<void> build(Environment environment) async {
+ for (File outputFile in environment.buildDir.listSync(recursive: true).whereType<File>()) {
+ if (!fs.path.basename(outputFile.path).contains('main.dart.js')) {
+ continue;
+ }
+ outputFile.copySync(
+ environment.outputDir.childFile(fs.path.basename(outputFile.path)).path
+ );
+ }
+ environment.projectDir
+ .childDirectory('web')
+ .childFile('index.html')
+ .copySync(fs.path.join(environment.outputDir.path, 'index.html'));
+ final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+ await assetBundle.build();
+ await copyAssets(assetBundle, environment, 'assets');
+ }
+}
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 8fdb708..c8048cb 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -12,6 +12,7 @@
import '../build_system/targets/ios.dart';
import '../build_system/targets/linux.dart';
import '../build_system/targets/macos.dart';
+import '../build_system/targets/web.dart';
import '../build_system/targets/windows.dart';
import '../globals.dart';
import '../project.dart';
@@ -31,6 +32,7 @@
DebugMacOSBundleFlutterAssets(),
ProfileMacOSBundleFlutterAssets(),
ReleaseMacOSBundleFlutterAssets(),
+ WebReleaseBundle(),
];
/// Assemble provides a low level API to interact with the flutter tool build
diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart
index a682b6b..c924c3e 100644
--- a/packages/flutter_tools/lib/src/web/compile.dart
+++ b/packages/flutter_tools/lib/src/web/compile.dart
@@ -2,17 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:archive/archive.dart';
import 'package:meta/meta.dart';
-import '../asset.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../build_info.dart';
-import '../bundle.dart';
+import '../build_system/build_system.dart';
+import '../build_system/targets/dart.dart';
+import '../build_system/targets/web.dart';
import '../globals.dart';
+import '../platform_plugins.dart';
+import '../plugins.dart';
import '../project.dart';
import '../reporting/reporting.dart';
@@ -23,64 +25,32 @@
if (!flutterProject.web.existsSync()) {
throwToolExit('Missing index.html.');
}
+ final bool hasWebPlugins = findPlugins(flutterProject)
+ .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
final Status status = logger.startProgress('Compiling $target for the Web...', timeout: null);
final Stopwatch sw = Stopwatch()..start();
- final Directory outputDir = fs.directory(getWebBuildDirectory())
- ..createSync(recursive: true);
- bool result;
- try {
- result = await webCompilationProxy.initialize(
- projectDirectory: FlutterProject.current().directory,
- mode: buildInfo.mode,
- projectName: flutterProject.manifest.appName,
- initializePlatform: initializePlatform,
- );
- if (result) {
- // Places assets adjacent to the web stuff.
- final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
- await assetBundle.build();
- await writeBundle(fs.directory(fs.path.join(outputDir.path, 'assets')), assetBundle.entries);
-
- // Copy results to output directory.
- final String outputPath = fs.path.join(
- flutterProject.dartTool.path,
- 'build',
- 'flutter_web',
- flutterProject.manifest.appName,
- '${fs.path.withoutExtension(target)}_web_entrypoint.dart.js',
- );
- // Check for deferred import outputs.
- final File dart2jsArchive = fs.file(fs.path.join(
- flutterProject.dartTool.path,
- 'build',
- 'flutter_web',
- '${flutterProject.manifest.appName}',
- '${fs.path.withoutExtension(target)}_web_entrypoint.dart.js.tar.gz'),
- );
- fs.file(outputPath).copySync(fs.path.join(outputDir.path, 'main.dart.js'));
- fs.file('$outputPath.map').copySync(fs.path.join(outputDir.path, 'main.dart.js.map'));
- flutterProject.web.indexFile.copySync(fs.path.join(outputDir.path, 'index.html'));
- if (dart2jsArchive.existsSync()) {
- final Archive archive = TarDecoder().decodeBytes(dart2jsArchive.readAsBytesSync());
- for (ArchiveFile file in archive.files) {
- outputDir.childFile(file.name).writeAsBytesSync(file.content);
- }
- }
+ final BuildResult result = await const BuildSystem().build(const WebReleaseBundle(), Environment(
+ outputDir: fs.directory(getWebBuildDirectory()),
+ projectDir: fs.currentDirectory,
+ buildDir: flutterProject.directory
+ .childDirectory('.dart_tool')
+ .childDirectory('flutter_build'),
+ defines: <String, String>{
+ kBuildMode: getNameForBuildMode(buildInfo.mode),
+ kTargetFile: target,
+ kInitializePlatform: initializePlatform.toString(),
+ kHasWebPlugins: hasWebPlugins.toString(),
+ },
+ ));
+ if (!result.success) {
+ for (ExceptionMeasurement measurement in result.exceptions.values) {
+ printError(measurement.stackTrace.toString());
+ printError(measurement.exception.toString());
}
- } catch (err) {
- printError(err.toString());
- result = false;
- } finally {
- status.stop();
+ throwToolExit('Failed to compile application for the Web.');
}
- if (result == false) {
- throwToolExit('Failed to compile $target for the Web.');
- }
- String buildName = 'ddc';
- if (buildInfo.isRelease) {
- buildName = 'dart2js';
- }
- flutterUsage.sendTiming('build', buildName, Duration(milliseconds: sw.elapsedMilliseconds));
+ status.stop();
+ flutterUsage.sendTiming('build', 'dart2js', Duration(milliseconds: sw.elapsedMilliseconds));
}
/// An indirection on web compilation.