mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Switch to assemble API for dart2js (#41447)
This commit is contained in:
parent
3df57a71b7
commit
ccc3dd968c
@ -282,10 +282,9 @@ Future<void> _runBuildTests() async {
|
||||
}
|
||||
// Web compilation tests.
|
||||
await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web'), path.join('lib', 'main.dart'));
|
||||
// Should fail to compile with dart:io.
|
||||
// Should not fail to compile with dart:io.
|
||||
await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web_compile_tests'),
|
||||
path.join('lib', 'dart_io_import.dart'),
|
||||
expectNonZeroExit: true,
|
||||
);
|
||||
|
||||
print('${bold}DONE: All build tests successful.$reset');
|
||||
|
@ -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_test/src/debug_test_builder.dart';
|
||||
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 @@ class FlutterWebEntrypointBuilder implements Builder {
|
||||
|
||||
@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> main() async {
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
||||
|
||||
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 '../globals.dart';
|
||||
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 @@ class ResidentWebRunner extends ResidentRunner {
|
||||
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 @@ class ResidentWebRunner extends ResidentRunner {
|
||||
).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;
|
||||
|
@ -35,6 +35,7 @@ import '../platform_plugins.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import '../web/chrome.dart';
|
||||
import '../web/compile.dart';
|
||||
|
||||
/// The name of the built web project.
|
||||
const String kBuildTargetName = 'web';
|
||||
@ -89,6 +90,11 @@ class WebFs {
|
||||
this._dwds,
|
||||
this.uri,
|
||||
this._assetServer,
|
||||
this._useBuildRunner,
|
||||
this._flutterProject,
|
||||
this._target,
|
||||
this._buildInfo,
|
||||
this._initializePlatform,
|
||||
);
|
||||
|
||||
/// The server uri.
|
||||
@ -98,12 +104,17 @@ class WebFs {
|
||||
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 @@ class WebFs {
|
||||
|
||||
/// 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 @@ class WebFs {
|
||||
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 @@ class WebFs {
|
||||
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 @@ class WebFs {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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 @@ class WebFs {
|
||||
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 @@ class AssetServer {
|
||||
return Response.notFound('');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
partFiles?.deleteSync(recursive: true);
|
||||
}
|
||||
|
@ -25,6 +25,13 @@ export 'source.dart';
|
||||
/// 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].
|
||||
|
@ -7,6 +7,7 @@ import 'dart:collection';
|
||||
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 @@ class FileHash {
|
||||
///
|
||||
/// 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,27 @@ class FileHashStore {
|
||||
|
||||
/// 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();
|
||||
await Future.wait(<Future<void>>[
|
||||
for (File file in files) _hashFile(file, dirty, Pool(kMaxOpenFiles))]);
|
||||
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);
|
||||
|
@ -50,7 +50,9 @@ class AssetBehavior extends SourceBehavior {
|
||||
|
||||
/// 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 @@ class AssetOutputBehavior extends SourceBehavior {
|
||||
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 @@ class AssetOutputBehavior extends SourceBehavior {
|
||||
);
|
||||
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 @@ class CopyAssets extends Target {
|
||||
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();
|
||||
|
@ -115,23 +115,7 @@ class CopyFlutterBundle extends Target {
|
||||
|
||||
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 @@ class CopyFlutterBundle extends Target {
|
||||
];
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
@ -336,7 +336,7 @@ abstract class MacOSBundleFlutterAssets extends Target {
|
||||
}
|
||||
// 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();
|
||||
|
203
packages/flutter_tools/lib/src/build_system/targets/web.dart
Normal file
203
packages/flutter_tools/lib/src/build_system/targets/web.dart
Normal file
@ -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');
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import '../build_system/targets/dart.dart';
|
||||
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 @@ const List<Target> _kDefaultTargets = <Target>[
|
||||
DebugMacOSBundleFlutterAssets(),
|
||||
ProfileMacOSBundleFlutterAssets(),
|
||||
ReleaseMacOSBundleFlutterAssets(),
|
||||
WebReleaseBundle(),
|
||||
];
|
||||
|
||||
/// Assemble provides a low level API to interact with the flutter tool build
|
||||
|
@ -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 @@ Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo bu
|
||||
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.
|
||||
|
@ -39,37 +39,37 @@ void main() {
|
||||
expect(fileStorage.version, 2);
|
||||
}));
|
||||
|
||||
test('saves and restores to file cache', () => testbed.run(() {
|
||||
test('saves and restores to file cache', () => testbed.run(() async {
|
||||
final File file = fs.file('foo.dart')
|
||||
..createSync()
|
||||
..writeAsStringSync('hello');
|
||||
final FileHashStore fileCache = FileHashStore(environment);
|
||||
fileCache.initialize();
|
||||
fileCache.hashFiles(<File>[file]);
|
||||
await fileCache.hashFiles(<File>[file]);
|
||||
fileCache.persist();
|
||||
final String currentHash = fileCache.currentHashes[file.resolveSymbolicLinksSync()];
|
||||
final String currentHash = fileCache.currentHashes[file.path];
|
||||
final List<int> buffer = fs.file(fs.path.join(environment.buildDir.path, '.filecache'))
|
||||
.readAsBytesSync();
|
||||
FileStorage fileStorage = FileStorage.fromBuffer(buffer);
|
||||
|
||||
expect(fileStorage.files.single.hash, currentHash);
|
||||
expect(fileStorage.files.single.path, file.resolveSymbolicLinksSync());
|
||||
expect(fileStorage.files.single.path, file.path);
|
||||
|
||||
|
||||
final FileHashStore newFileCache = FileHashStore(environment);
|
||||
newFileCache.initialize();
|
||||
expect(newFileCache.currentHashes, isEmpty);
|
||||
expect(newFileCache.previousHashes[fs.path.absolute('foo.dart')], currentHash);
|
||||
expect(newFileCache.previousHashes['foo.dart'], currentHash);
|
||||
newFileCache.persist();
|
||||
|
||||
// Still persisted correctly.
|
||||
fileStorage = FileStorage.fromBuffer(buffer);
|
||||
|
||||
expect(fileStorage.files.single.hash, currentHash);
|
||||
expect(fileStorage.files.single.path, file.resolveSymbolicLinksSync());
|
||||
expect(fileStorage.files.single.path, file.path);
|
||||
}));
|
||||
|
||||
test('handles persisting with a missing build directory', () => testbed.run(() {
|
||||
test('handles persisting with a missing build directory', () => testbed.run(() async {
|
||||
final File file = fs.file('foo.dart')
|
||||
..createSync()
|
||||
..writeAsStringSync('hello');
|
||||
@ -77,7 +77,7 @@ void main() {
|
||||
fileCache.initialize();
|
||||
environment.buildDir.deleteSync(recursive: true);
|
||||
|
||||
fileCache.hashFiles(<File>[file]);
|
||||
await fileCache.hashFiles(<File>[file]);
|
||||
// Does not throw.
|
||||
fileCache.persist();
|
||||
}));
|
||||
|
@ -0,0 +1,221 @@
|
||||
// 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/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/process_manager.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/dart.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/web.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
import '../../../src/mocks.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
Testbed testbed;
|
||||
Environment environment;
|
||||
MockPlatform mockPlatform;
|
||||
MockPlatform mockWindowsPlatform;
|
||||
|
||||
setUp(() {
|
||||
mockPlatform = MockPlatform();
|
||||
mockWindowsPlatform = MockPlatform();
|
||||
|
||||
when(mockPlatform.isWindows).thenReturn(false);
|
||||
when(mockPlatform.isMacOS).thenReturn(true);
|
||||
when(mockPlatform.isLinux).thenReturn(false);
|
||||
|
||||
when(mockWindowsPlatform.isWindows).thenReturn(true);
|
||||
when(mockWindowsPlatform.isMacOS).thenReturn(false);
|
||||
when(mockWindowsPlatform.isLinux).thenReturn(false);
|
||||
|
||||
testbed = Testbed(setup: () {
|
||||
environment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
outputDir: fs.currentDirectory,
|
||||
buildDir: fs.currentDirectory,
|
||||
defines: <String, String>{
|
||||
kTargetFile: fs.path.join('lib', 'main.dart'),
|
||||
}
|
||||
);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
}, overrides: <Type, Generator>{
|
||||
Platform: () => mockPlatform,
|
||||
});
|
||||
});
|
||||
|
||||
test('WebEntrypointTarget generates an entrypoint with plugins and init platform', () => testbed.run(() async {
|
||||
environment.defines[kHasWebPlugins] = 'true';
|
||||
environment.defines[kInitializePlatform] = 'true';
|
||||
await const WebEntrypointTarget().build(environment);
|
||||
|
||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||
|
||||
// Plugins
|
||||
expect(generated, contains("import 'generated_plugin_registrant.dart';"));
|
||||
expect(generated, contains('registerPlugins(webPluginRegistry);'));
|
||||
|
||||
// Platform
|
||||
expect(generated, contains('if (true) {'));
|
||||
|
||||
// Main
|
||||
expect(generated, contains('entrypoint.main();'));
|
||||
|
||||
// Import.
|
||||
expect(generated, contains('import "file:///lib/main.dart" as entrypoint;'));
|
||||
}));
|
||||
|
||||
test('WebEntrypointTarget generates an entrypoint with plugins and init platform on windows', () => testbed.run(() async {
|
||||
environment.defines[kHasWebPlugins] = 'true';
|
||||
environment.defines[kInitializePlatform] = 'true';
|
||||
await const WebEntrypointTarget().build(environment);
|
||||
|
||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||
|
||||
// Plugins
|
||||
expect(generated, contains("import 'generated_plugin_registrant.dart';"));
|
||||
expect(generated, contains('registerPlugins(webPluginRegistry);'));
|
||||
|
||||
// Platform
|
||||
expect(generated, contains('if (true) {'));
|
||||
|
||||
// Main
|
||||
expect(generated, contains('entrypoint.main();'));
|
||||
|
||||
// Import.
|
||||
expect(generated, contains('import "file:///C:/lib/main.dart" as entrypoint;'));
|
||||
}, overrides: <Type, Generator>{
|
||||
Platform: () => mockWindowsPlatform,
|
||||
}));
|
||||
|
||||
test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async {
|
||||
environment.defines[kHasWebPlugins] = 'false';
|
||||
environment.defines[kInitializePlatform] = 'true';
|
||||
await const WebEntrypointTarget().build(environment);
|
||||
|
||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||
|
||||
// Plugins
|
||||
expect(generated, isNot(contains("import 'generated_plugin_registrant.dart';")));
|
||||
expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));
|
||||
|
||||
// Platform
|
||||
expect(generated, contains('if (true) {'));
|
||||
|
||||
// Main
|
||||
expect(generated, contains('entrypoint.main();'));
|
||||
}));
|
||||
|
||||
test('WebEntrypointTarget generates an entrypoint with plugins and without init platform', () => testbed.run(() async {
|
||||
environment.defines[kHasWebPlugins] = 'true';
|
||||
environment.defines[kInitializePlatform] = 'false';
|
||||
await const WebEntrypointTarget().build(environment);
|
||||
|
||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||
|
||||
// Plugins
|
||||
expect(generated, contains("import 'generated_plugin_registrant.dart';"));
|
||||
expect(generated, contains('registerPlugins(webPluginRegistry);'));
|
||||
|
||||
// Platform
|
||||
expect(generated, contains('if (false) {'));
|
||||
|
||||
// Main
|
||||
expect(generated, contains('entrypoint.main();'));
|
||||
}));
|
||||
|
||||
test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async {
|
||||
environment.defines[kHasWebPlugins] = 'false';
|
||||
environment.defines[kInitializePlatform] = 'false';
|
||||
await const WebEntrypointTarget().build(environment);
|
||||
|
||||
final String generated = environment.buildDir.childFile('main.dart').readAsStringSync();
|
||||
|
||||
// Plugins
|
||||
expect(generated, isNot(contains("import 'generated_plugin_registrant.dart';")));
|
||||
expect(generated, isNot(contains('registerPlugins(webPluginRegistry);')));
|
||||
|
||||
// Platform
|
||||
expect(generated, contains('if (false) {'));
|
||||
|
||||
// Main
|
||||
expect(generated, contains('entrypoint.main();'));
|
||||
}));
|
||||
|
||||
test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async {
|
||||
environment.defines[kBuildMode] = 'profile';
|
||||
when(processManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
return FakeProcessResult(exitCode: 0);
|
||||
});
|
||||
await const Dart2JSTarget().build(environment);
|
||||
|
||||
final List<String> expected = <String>[
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
|
||||
'--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
|
||||
'-O1', // lowest optimizations.
|
||||
'-o',
|
||||
environment.buildDir.childFile('main.dart.js').absolute.path,
|
||||
'--packages=.packages',
|
||||
'-Ddart.vm.profile=true',
|
||||
environment.buildDir.childFile('main.dart').absolute.path,
|
||||
];
|
||||
verify(processManager.run(expected)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
}));
|
||||
|
||||
test('Dart2JSTarget calls dart2js with expected args in release mode', () => testbed.run(() async {
|
||||
environment.defines[kBuildMode] = 'release';
|
||||
when(processManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
return FakeProcessResult(exitCode: 0);
|
||||
});
|
||||
await const Dart2JSTarget().build(environment);
|
||||
|
||||
final List<String> expected = <String>[
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
|
||||
'--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
|
||||
'-O4', // highest optimizations.
|
||||
'-o',
|
||||
environment.buildDir.childFile('main.dart.js').absolute.path,
|
||||
'--packages=.packages',
|
||||
'-Ddart.vm.product=true',
|
||||
environment.buildDir.childFile('main.dart').absolute.path,
|
||||
];
|
||||
verify(processManager.run(expected)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
}));
|
||||
|
||||
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(processManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
return FakeProcessResult(exitCode: 0);
|
||||
});
|
||||
await const Dart2JSTarget().build(environment);
|
||||
|
||||
final List<String> expected = <String>[
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
|
||||
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'snapshots', 'dart2js.dart.snapshot'),
|
||||
'--libraries-spec=' + fs.path.join('bin', 'cache', 'flutter_web_sdk', 'libraries.json'),
|
||||
'-O3', // configured optimizations.
|
||||
'-o',
|
||||
environment.buildDir.childFile('main.dart.js').absolute.path,
|
||||
'--packages=.packages',
|
||||
'-Ddart.vm.product=true',
|
||||
environment.buildDir.childFile('main.dart').absolute.path,
|
||||
];
|
||||
verify(processManager.run(expected)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
}));
|
||||
}
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockPlatform extends Mock implements Platform {}
|
@ -2,11 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
@ -26,10 +22,8 @@ import '../../src/common.dart';
|
||||
import '../../src/testbed.dart';
|
||||
|
||||
void main() {
|
||||
MockWebCompilationProxy mockWebCompilationProxy;
|
||||
Testbed testbed;
|
||||
MockPlatform mockPlatform;
|
||||
bool addArchive = false;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.flutterRoot = '';
|
||||
@ -37,8 +31,6 @@ void main() {
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
addArchive = false;
|
||||
mockWebCompilationProxy = MockWebCompilationProxy();
|
||||
testbed = Testbed(setup: () {
|
||||
fs.file('pubspec.yaml')
|
||||
..createSync()
|
||||
@ -46,47 +38,13 @@ void main() {
|
||||
fs.file('.packages').createSync();
|
||||
fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
|
||||
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
||||
when(mockWebCompilationProxy.initialize(
|
||||
projectName: anyNamed('projectName'),
|
||||
projectDirectory: anyNamed('projectDirectory'),
|
||||
mode: anyNamed('mode'),
|
||||
initializePlatform: anyNamed('initializePlatform'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
final String prefix = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib');
|
||||
final String path = fs.path.join(prefix, 'main_web_entrypoint.dart.js');
|
||||
fs.file(path).createSync(recursive: true);
|
||||
fs.file('$path.map').createSync();
|
||||
if (addArchive) {
|
||||
final List<int> bytes = utf8.encode('void main() {}');
|
||||
final TarEncoder encoder = TarEncoder();
|
||||
final Archive archive = Archive()
|
||||
..addFile(ArchiveFile.noCompress('main_web_entrypoint.1.dart.js', bytes.length, bytes));
|
||||
fs.file(fs.path.join(prefix, 'main_web_entrypoint.dart.js.tar.gz'))
|
||||
..writeAsBytes(encoder.encode(archive));
|
||||
}
|
||||
return Future<bool>.value(true);
|
||||
});
|
||||
}, overrides: <Type, Generator>{
|
||||
WebCompilationProxy: () => mockWebCompilationProxy,
|
||||
Platform: () => mockPlatform,
|
||||
FlutterVersion: () => MockFlutterVersion(),
|
||||
FeatureFlags: () => TestFeatureFlags(isWebEnabled: true),
|
||||
});
|
||||
});
|
||||
|
||||
test('Copies generated part files out of build directory', () => testbed.run(() async {
|
||||
addArchive = true;
|
||||
await buildWeb(
|
||||
FlutterProject.current(),
|
||||
fs.path.join('lib', 'main.dart'),
|
||||
BuildInfo.release,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(fs.file(fs.path.join('build', 'web', 'main_web_entrypoint.1.dart.js')), exists);
|
||||
expect(fs.file(fs.path.join('build', 'web', 'main.dart.js')), exists);
|
||||
}));
|
||||
|
||||
test('Refuses to build for web when missing index.html', () => testbed.run(() async {
|
||||
fs.file(fs.path.join('web', 'index.html')).deleteSync();
|
||||
|
||||
|
@ -22,7 +22,7 @@ void main() {
|
||||
fs.file(fs.path.join('web', 'index.html'))
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('hello');
|
||||
assetServer = AssetServer(FlutterProject.current(), fs.path.join('main'));
|
||||
assetServer = DebugAssetServer(FlutterProject.current(), fs.path.join('main'));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user