mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Remove most of the target logic for build web, cleanup rules (#34589)
This commit is contained in:
parent
1d0aa02ac7
commit
7472fad194
459
packages/flutter_tools/lib/src/build_runner/build_script.dart
Normal file
459
packages/flutter_tools/lib/src/build_runner/build_script.dart
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// 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/analyzer.dart'; // ignore: deprecated_member_use
|
||||||
|
import 'package:build_runner/build_runner.dart' as build_runner;
|
||||||
|
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_core/build_runner_core.dart' as core;
|
||||||
|
import 'package:build_test/builder.dart';
|
||||||
|
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: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';
|
||||||
|
const String jsEntrypointExtension = '.dart.js';
|
||||||
|
const String jsEntrypointSourceMapExtension = '.dart.js.map';
|
||||||
|
const String jsEntrypointArchiveExtension = '.dart.js.tar.gz';
|
||||||
|
const String digestsEntrypointExtension = '.digests';
|
||||||
|
const String jsModuleErrorsExtension = '.ddc.js.errors';
|
||||||
|
const String jsModuleExtension = '.ddc.js';
|
||||||
|
const String jsSourceMapExtension = '.ddc.js.map';
|
||||||
|
|
||||||
|
final DartPlatform flutterWebPlatform =
|
||||||
|
DartPlatform.register('flutter_web', <String>[
|
||||||
|
'async',
|
||||||
|
'collection',
|
||||||
|
'convert',
|
||||||
|
'core',
|
||||||
|
'developer',
|
||||||
|
'html',
|
||||||
|
'html_common',
|
||||||
|
'indexed_db',
|
||||||
|
'js',
|
||||||
|
'js_util',
|
||||||
|
'math',
|
||||||
|
'svg',
|
||||||
|
'typed_data',
|
||||||
|
'web_audio',
|
||||||
|
'web_gl',
|
||||||
|
'web_sql',
|
||||||
|
'_internal',
|
||||||
|
// Flutter web specific libraries.
|
||||||
|
'ui',
|
||||||
|
'_engine',
|
||||||
|
'io',
|
||||||
|
'isolate',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/// The builders required to compile a Flutter application to the web.
|
||||||
|
final List<core.BuilderApplication> builders = <core.BuilderApplication>[
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:test_bootstrap',
|
||||||
|
<BuilderFactory>[
|
||||||
|
(BuilderOptions options) => const DebugTestBuilder(),
|
||||||
|
(BuilderOptions options) => const FlutterWebTestBootstrapBuilder(),
|
||||||
|
],
|
||||||
|
core.toRoot(),
|
||||||
|
hideOutput: true,
|
||||||
|
defaultGenerateFor: const InputSet(
|
||||||
|
include: <String>[
|
||||||
|
'test/**',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:shell',
|
||||||
|
<BuilderFactory>[
|
||||||
|
(BuilderOptions options) => const FlutterWebShellBuilder(),
|
||||||
|
],
|
||||||
|
core.toRoot(),
|
||||||
|
hideOutput: true,
|
||||||
|
defaultGenerateFor: const InputSet(
|
||||||
|
include: <String>[
|
||||||
|
'lib/**',
|
||||||
|
'web/**',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:module_library',
|
||||||
|
<Builder Function(BuilderOptions)>[moduleLibraryBuilder],
|
||||||
|
core.toAllPackages(),
|
||||||
|
isOptional: true,
|
||||||
|
hideOutput: true,
|
||||||
|
appliesBuilders: <String>['flutter_tools:module_cleanup']),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:ddc_modules',
|
||||||
|
<Builder Function(BuilderOptions)>[
|
||||||
|
(BuilderOptions options) => MetaModuleBuilder(flutterWebPlatform),
|
||||||
|
(BuilderOptions options) => MetaModuleCleanBuilder(flutterWebPlatform),
|
||||||
|
(BuilderOptions options) => ModuleBuilder(flutterWebPlatform),
|
||||||
|
],
|
||||||
|
core.toNoneByDefault(),
|
||||||
|
isOptional: true,
|
||||||
|
hideOutput: true,
|
||||||
|
appliesBuilders: <String>['flutter_tools:module_cleanup']),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:ddc',
|
||||||
|
<Builder Function(BuilderOptions)>[
|
||||||
|
(BuilderOptions builderOptions) => KernelBuilder(
|
||||||
|
platformSdk: builderOptions.config['flutterWebSdk'],
|
||||||
|
summaryOnly: true,
|
||||||
|
sdkKernelPath: path.join('kernel', 'flutter_ddc_sdk.dill'),
|
||||||
|
outputExtension: ddcKernelExtension,
|
||||||
|
platform: flutterWebPlatform,
|
||||||
|
librariesPath: 'libraries.json',
|
||||||
|
),
|
||||||
|
(BuilderOptions builderOptions) => DevCompilerBuilder(
|
||||||
|
useIncrementalCompiler: true,
|
||||||
|
platform: flutterWebPlatform,
|
||||||
|
platformSdk: builderOptions.config['flutterWebSdk'],
|
||||||
|
sdkKernelPath: path.join('kernel', 'flutter_ddc_sdk.dill'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
core.toAllPackages(),
|
||||||
|
isOptional: true,
|
||||||
|
hideOutput: true,
|
||||||
|
appliesBuilders: <String>['flutter_tools:ddc_modules']),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:entrypoint',
|
||||||
|
<BuilderFactory>[
|
||||||
|
(BuilderOptions options) => FlutterWebEntrypointBuilder(
|
||||||
|
options.config['release'] ?? false,
|
||||||
|
options.config['flutterWebSdk'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
core.toRoot(),
|
||||||
|
hideOutput: true,
|
||||||
|
defaultGenerateFor: const InputSet(
|
||||||
|
include: <String>[
|
||||||
|
'lib/**_web_entrypoint.dart',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
core.apply(
|
||||||
|
'flutter_tools:test_entrypoint',
|
||||||
|
<BuilderFactory>[
|
||||||
|
(BuilderOptions options) => const FlutterWebTestEntrypointBuilder(),
|
||||||
|
],
|
||||||
|
core.toRoot(),
|
||||||
|
hideOutput: true,
|
||||||
|
defaultGenerateFor: const InputSet(
|
||||||
|
include: <String>[
|
||||||
|
'test/**_test.dart.browser_test.dart',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
core.applyPostProcess('flutter_tools:module_cleanup', moduleCleanup,
|
||||||
|
defaultGenerateFor: const InputSet())
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The entrypoint to this build script.
|
||||||
|
Future<void> main(List<String> args, [SendPort sendPort]) async {
|
||||||
|
core.overrideGeneratedOutputDirectory('flutter_web');
|
||||||
|
final int result = await build_runner.run(args, builders);
|
||||||
|
sendPort?.send(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A ddc-only entrypoint builder that respects the Flutter target flag.
|
||||||
|
class FlutterWebTestEntrypointBuilder implements Builder {
|
||||||
|
const FlutterWebTestEntrypointBuilder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
||||||
|
'.dart': <String>[
|
||||||
|
ddcBootstrapExtension,
|
||||||
|
jsEntrypointExtension,
|
||||||
|
jsEntrypointSourceMapExtension,
|
||||||
|
jsEntrypointArchiveExtension,
|
||||||
|
digestsEntrypointExtension,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build(BuildStep buildStep) async {
|
||||||
|
log.info('building for target ${buildStep.inputId.path}');
|
||||||
|
await bootstrapDdc(buildStep, platform: flutterWebPlatform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A ddc-only entrypoint builder that respects the Flutter target flag.
|
||||||
|
class FlutterWebEntrypointBuilder implements Builder {
|
||||||
|
const FlutterWebEntrypointBuilder(this.release, this.flutterWebSdk);
|
||||||
|
|
||||||
|
final bool release;
|
||||||
|
final String flutterWebSdk;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
||||||
|
'.dart': <String>[
|
||||||
|
ddcBootstrapExtension,
|
||||||
|
jsEntrypointExtension,
|
||||||
|
jsEntrypointSourceMapExtension,
|
||||||
|
jsEntrypointArchiveExtension,
|
||||||
|
digestsEntrypointExtension,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build(BuildStep buildStep) async {
|
||||||
|
if (release) {
|
||||||
|
await bootstrapDart2Js(buildStep, flutterWebSdk);
|
||||||
|
} else {
|
||||||
|
await bootstrapDdc(buildStep, platform: flutterWebPlatform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bootstraps the test entrypoint.
|
||||||
|
class FlutterWebTestBootstrapBuilder implements Builder {
|
||||||
|
const FlutterWebTestBootstrapBuilder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
||||||
|
'_test.dart': <String>[
|
||||||
|
'_test.dart.browser_test.dart',
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build(BuildStep buildStep) async {
|
||||||
|
final AssetId id = buildStep.inputId;
|
||||||
|
final String contents = await buildStep.readAsString(id);
|
||||||
|
final String assetPath = id.pathSegments.first == 'lib'
|
||||||
|
? path.url.join('packages', id.package, id.path)
|
||||||
|
: id.path;
|
||||||
|
final Metadata metadata = parseMetadata(
|
||||||
|
assetPath, contents, Runtime.builtIn.map((Runtime runtime) => runtime.name).toSet());
|
||||||
|
|
||||||
|
if (metadata.testOn.evaluate(SuitePlatform(Runtime.chrome))) {
|
||||||
|
await buildStep.writeAsString(id.addExtension('.browser_test.dart'), '''
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'dart:html';
|
||||||
|
import 'dart:js';
|
||||||
|
|
||||||
|
import 'package:stream_channel/stream_channel.dart';
|
||||||
|
import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports
|
||||||
|
import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
|
||||||
|
import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports
|
||||||
|
import 'package:test_api/src/suite_channel_manager.dart'; // ignore: implementation_imports
|
||||||
|
|
||||||
|
import "${path.url.basename(id.path)}" as test;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
// Extra initialization for flutter_web.
|
||||||
|
// The following parameters are hard-coded in Flutter's test embedder. Since
|
||||||
|
// we don't have an embedder yet this is the lowest-most layer we can put
|
||||||
|
// this stuff in.
|
||||||
|
await ui.webOnlyInitializeEngine();
|
||||||
|
// TODO(flutterweb): remove need for dynamic cast.
|
||||||
|
(ui.window as dynamic).debugOverrideDevicePixelRatio(3.0);
|
||||||
|
(ui.window as dynamic).webOnlyDebugPhysicalSizeOverride = const ui.Size(2400, 1800);
|
||||||
|
internalBootstrapBrowserTest(() => test.main);
|
||||||
|
}
|
||||||
|
|
||||||
|
void internalBootstrapBrowserTest(Function getMain()) {
|
||||||
|
var channel =
|
||||||
|
serializeSuite(getMain, hidePrints: false, beforeLoad: () async {
|
||||||
|
var serialized =
|
||||||
|
await suiteChannel("test.browser.mapper").stream.first as Map;
|
||||||
|
if (serialized == null) return;
|
||||||
|
});
|
||||||
|
postMessageChannel().pipe(channel);
|
||||||
|
}
|
||||||
|
StreamChannel serializeSuite(Function getMain(),
|
||||||
|
{bool hidePrints = true, Future beforeLoad()}) =>
|
||||||
|
RemoteListener.start(getMain,
|
||||||
|
hidePrints: hidePrints, beforeLoad: beforeLoad);
|
||||||
|
|
||||||
|
StreamChannel suiteChannel(String name) {
|
||||||
|
var manager = SuiteChannelManager.current;
|
||||||
|
if (manager == null) {
|
||||||
|
throw StateError('suiteChannel() may only be called within a test worker.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager.connectOut(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamChannel postMessageChannel() {
|
||||||
|
var controller = StreamChannelController(sync: true);
|
||||||
|
window.onMessage.firstWhere((message) {
|
||||||
|
return message.origin == window.location.origin && message.data == "port";
|
||||||
|
}).then((message) {
|
||||||
|
var port = message.ports.first;
|
||||||
|
var portSubscription = port.onMessage.listen((message) {
|
||||||
|
controller.local.sink.add(message.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.local.stream.listen((data) {
|
||||||
|
port.postMessage({"data": data});
|
||||||
|
}, onDone: () {
|
||||||
|
port.postMessage({"event": "done"});
|
||||||
|
portSubscription.cancel();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context['parent'].callMethod('postMessage', [
|
||||||
|
JsObject.jsify({"href": window.location.href, "ready": true}),
|
||||||
|
window.location.origin,
|
||||||
|
]);
|
||||||
|
return controller.foreign;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStackTraceMapper(StackTraceMapper mapper) {
|
||||||
|
var formatter = StackTraceFormatter.current;
|
||||||
|
if (formatter == null) {
|
||||||
|
throw StateError(
|
||||||
|
'setStackTraceMapper() may only be called within a test worker.');
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter.configure(mapper: mapper);
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shell builder which generates the web specific entrypoint.
|
||||||
|
class FlutterWebShellBuilder implements Builder {
|
||||||
|
const FlutterWebShellBuilder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build(BuildStep buildStep) async {
|
||||||
|
final AssetId dartEntrypointId = buildStep.inputId;
|
||||||
|
final bool isAppEntrypoint = await _isAppEntryPoint(dartEntrypointId, buildStep);
|
||||||
|
if (!isAppEntrypoint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final AssetId outputId = buildStep.inputId.changeExtension('_web_entrypoint.dart');
|
||||||
|
await buildStep.writeAsString(outputId, '''
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import "${path.url.basename(buildStep.inputId.path)}" as entrypoint;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
await ui.webOnlyInitializePlatform();
|
||||||
|
entrypoint.main();
|
||||||
|
}
|
||||||
|
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
||||||
|
'.dart': <String>['_web_entrypoint.dart'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> bootstrapDart2Js(BuildStep buildStep, String flutterWebSdk) 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: false)..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 = await _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"',
|
||||||
|
'-O4',
|
||||||
|
'-o',
|
||||||
|
'$jsOutputPath',
|
||||||
|
'--packages="$packageFile"',
|
||||||
|
'-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()) {
|
||||||
|
log.info(result.output);
|
||||||
|
// Explicitly write out the original js file and sourcemap.
|
||||||
|
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`.
|
||||||
|
Future<String> _createPackageFile(Iterable<AssetId> inputSources, BuildStep buildStep, ScratchSpace scratchSpace) async {
|
||||||
|
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');
|
||||||
|
await packagesFile
|
||||||
|
.writeAsString('# 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 {
|
||||||
|
assert(dartId.extension == '.dart');
|
||||||
|
// Skip reporting errors here, dartdevc will report them later with nicer
|
||||||
|
// formatting.
|
||||||
|
final CompilationUnit parsed = parseCompilationUnit(await reader.readAsString(dartId),
|
||||||
|
suppressErrors: true);
|
||||||
|
// Allow two or fewer arguments so that entrypoints intended for use with
|
||||||
|
// [spawnUri] get counted.
|
||||||
|
return parsed.declarations.any((CompilationUnitMember node) {
|
||||||
|
return node is FunctionDeclaration &&
|
||||||
|
node.name.name == 'main' &&
|
||||||
|
node.functionExpression.parameters.parameters.length <= 2;
|
||||||
|
});
|
||||||
|
}
|
@ -4,31 +4,16 @@
|
|||||||
|
|
||||||
// ignore_for_file: implementation_imports
|
// ignore_for_file: implementation_imports
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' as io; // ignore: dart_io_import
|
|
||||||
|
|
||||||
import 'package:build/build.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_core/build_runner_core.dart' as core;
|
import 'package:build_runner_core/build_runner_core.dart' as core;
|
||||||
import 'package:build_runner_core/src/asset_graph/graph.dart';
|
import 'package:build_runner_core/src/asset_graph/graph.dart';
|
||||||
import 'package:build_runner_core/src/asset_graph/node.dart';
|
import 'package:build_runner_core/src/asset_graph/node.dart';
|
||||||
import 'package:build_runner_core/src/generate/build_impl.dart';
|
import 'package:build_runner_core/src/generate/build_impl.dart';
|
||||||
import 'package:build_runner_core/src/generate/options.dart';
|
import 'package:build_runner_core/src/generate/options.dart';
|
||||||
import 'package:build_test/builder.dart';
|
|
||||||
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:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:scratch_space/scratch_space.dart';
|
|
||||||
import 'package:test_core/backend.dart';
|
|
||||||
import 'package:watcher/watcher.dart';
|
import 'package:watcher/watcher.dart';
|
||||||
|
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
@ -36,152 +21,10 @@ import '../base/file_system.dart';
|
|||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
import '../base/platform.dart';
|
import '../base/platform.dart';
|
||||||
import '../compile.dart';
|
import '../compile.dart';
|
||||||
import '../convert.dart';
|
|
||||||
import '../dart/package_map.dart';
|
import '../dart/package_map.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import '../web/compile.dart';
|
import '../web/compile.dart';
|
||||||
|
import 'build_script.dart';
|
||||||
const String ddcBootstrapExtension = '.dart.bootstrap.js';
|
|
||||||
const String jsEntrypointExtension = '.dart.js';
|
|
||||||
const String jsEntrypointSourceMapExtension = '.dart.js.map';
|
|
||||||
const String jsEntrypointArchiveExtension = '.dart.js.tar.gz';
|
|
||||||
const String digestsEntrypointExtension = '.digests';
|
|
||||||
const String jsModuleErrorsExtension = '.ddc.js.errors';
|
|
||||||
const String jsModuleExtension = '.ddc.js';
|
|
||||||
const String jsSourceMapExtension = '.ddc.js.map';
|
|
||||||
|
|
||||||
final DartPlatform flutterWebPlatform =
|
|
||||||
DartPlatform.register('flutter_web', <String>[
|
|
||||||
'async',
|
|
||||||
'collection',
|
|
||||||
'convert',
|
|
||||||
'core',
|
|
||||||
'developer',
|
|
||||||
'html',
|
|
||||||
'html_common',
|
|
||||||
'indexed_db',
|
|
||||||
'js',
|
|
||||||
'js_util',
|
|
||||||
'math',
|
|
||||||
'svg',
|
|
||||||
'typed_data',
|
|
||||||
'web_audio',
|
|
||||||
'web_gl',
|
|
||||||
'web_sql',
|
|
||||||
'_internal',
|
|
||||||
// Flutter web specific libraries.
|
|
||||||
'ui',
|
|
||||||
'_engine',
|
|
||||||
'io',
|
|
||||||
'isolate',
|
|
||||||
]);
|
|
||||||
|
|
||||||
/// The build application to compile a flutter application to the web.
|
|
||||||
final List<core.BuilderApplication> builders = <core.BuilderApplication>[
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|test_bootstrap',
|
|
||||||
<BuilderFactory>[
|
|
||||||
(BuilderOptions options) => const DebugTestBuilder(),
|
|
||||||
(BuilderOptions options) => const FlutterWebTestBootstrapBuilder(),
|
|
||||||
],
|
|
||||||
core.toRoot(),
|
|
||||||
hideOutput: true,
|
|
||||||
defaultGenerateFor: const InputSet(
|
|
||||||
include: <String>[
|
|
||||||
'test/**',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|shell',
|
|
||||||
<BuilderFactory>[
|
|
||||||
(BuilderOptions options) => FlutterWebShellBuilder(
|
|
||||||
options.config['targets'] ?? <String>['lib/main.dart']
|
|
||||||
),
|
|
||||||
],
|
|
||||||
core.toRoot(),
|
|
||||||
hideOutput: true,
|
|
||||||
defaultGenerateFor: const InputSet(
|
|
||||||
include: <String>[
|
|
||||||
'lib/**',
|
|
||||||
'web/**',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|module_library',
|
|
||||||
<Builder Function(BuilderOptions)>[moduleLibraryBuilder],
|
|
||||||
core.toAllPackages(),
|
|
||||||
isOptional: true,
|
|
||||||
hideOutput: true,
|
|
||||||
appliesBuilders: <String>['flutter_tools|module_cleanup']),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|ddc_modules',
|
|
||||||
<Builder Function(BuilderOptions)>[
|
|
||||||
(BuilderOptions options) => MetaModuleBuilder(flutterWebPlatform),
|
|
||||||
(BuilderOptions options) => MetaModuleCleanBuilder(flutterWebPlatform),
|
|
||||||
(BuilderOptions options) => ModuleBuilder(flutterWebPlatform),
|
|
||||||
],
|
|
||||||
core.toNoneByDefault(),
|
|
||||||
isOptional: true,
|
|
||||||
hideOutput: true,
|
|
||||||
appliesBuilders: <String>['flutter_tools|module_cleanup']),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|ddc',
|
|
||||||
<Builder Function(BuilderOptions)>[
|
|
||||||
(BuilderOptions builderOptions) => KernelBuilder(
|
|
||||||
platformSdk: artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
|
||||||
summaryOnly: true,
|
|
||||||
sdkKernelPath: path.join('kernel', 'flutter_ddc_sdk.dill'),
|
|
||||||
outputExtension: ddcKernelExtension,
|
|
||||||
platform: flutterWebPlatform,
|
|
||||||
librariesPath: 'libraries.json',
|
|
||||||
),
|
|
||||||
(BuilderOptions builderOptions) => DevCompilerBuilder(
|
|
||||||
useIncrementalCompiler: false,
|
|
||||||
platform: flutterWebPlatform,
|
|
||||||
platformSdk: artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
|
||||||
sdkKernelPath: path.join('kernel', 'flutter_ddc_sdk.dill'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
core.toAllPackages(),
|
|
||||||
isOptional: true,
|
|
||||||
hideOutput: true,
|
|
||||||
appliesBuilders: <String>['flutter_tools|ddc_modules']),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|entrypoint',
|
|
||||||
<BuilderFactory>[
|
|
||||||
(BuilderOptions options) => FlutterWebEntrypointBuilder(
|
|
||||||
options.config['targets'] ?? <String>['lib/main.dart'],
|
|
||||||
options.config['release'],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
core.toRoot(),
|
|
||||||
hideOutput: true,
|
|
||||||
defaultGenerateFor: const InputSet(
|
|
||||||
include: <String>[
|
|
||||||
'lib/**',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
core.apply(
|
|
||||||
'flutter_tools|test_entrypoint',
|
|
||||||
<BuilderFactory>[
|
|
||||||
(BuilderOptions options) => FlutterWebTestEntrypointBuilder(
|
|
||||||
options.config['targets'] ?? const <String>[]
|
|
||||||
),
|
|
||||||
],
|
|
||||||
core.toRoot(),
|
|
||||||
hideOutput: true,
|
|
||||||
defaultGenerateFor: const InputSet(
|
|
||||||
include: <String>[
|
|
||||||
'test/**_test.dart.browser_test.dart',
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
core.applyPostProcess('flutter_tools|module_cleanup', moduleCleanup,
|
|
||||||
defaultGenerateFor: const InputSet())
|
|
||||||
];
|
|
||||||
|
|
||||||
/// A build_runner specific implementation of the [WebCompilationProxy].
|
/// A build_runner specific implementation of the [WebCompilationProxy].
|
||||||
class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
||||||
@ -193,8 +36,7 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> initialize({
|
Future<bool> initialize({
|
||||||
@required Directory projectDirectory,
|
Directory projectDirectory,
|
||||||
@required List<String> targets,
|
|
||||||
String testOutputDir,
|
String testOutputDir,
|
||||||
bool release = false,
|
bool release = false,
|
||||||
}) async {
|
}) async {
|
||||||
@ -238,14 +80,11 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
final Status status =
|
|
||||||
logger.startProgress('Compiling ${targets.first} for the Web...', timeout: null);
|
|
||||||
core.BuildResult result;
|
core.BuildResult result;
|
||||||
try {
|
try {
|
||||||
result = await _runBuilder(
|
result = await _runBuilder(
|
||||||
buildEnvironment,
|
buildEnvironment,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
targets,
|
|
||||||
release,
|
release,
|
||||||
buildDirs,
|
buildDirs,
|
||||||
);
|
);
|
||||||
@ -255,7 +94,6 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
result = await _runBuilder(
|
result = await _runBuilder(
|
||||||
buildEnvironment,
|
buildEnvironment,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
targets,
|
|
||||||
release,
|
release,
|
||||||
buildDirs,
|
buildDirs,
|
||||||
);
|
);
|
||||||
@ -265,13 +103,10 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
result = await _runBuilder(
|
result = await _runBuilder(
|
||||||
buildEnvironment,
|
buildEnvironment,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
targets,
|
|
||||||
release,
|
release,
|
||||||
buildDirs,
|
buildDirs,
|
||||||
);
|
);
|
||||||
return result.status == core.BuildStatus.success;
|
return result.status == core.BuildStatus.success;
|
||||||
} finally {
|
|
||||||
status.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,24 +128,22 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
return result.status == core.BuildStatus.success;
|
return result.status == core.BuildStatus.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<core.BuildResult> _runBuilder(core.BuildEnvironment buildEnvironment, BuildOptions buildOptions, bool release, Set<core.BuildDirectory> buildDirs) async {
|
||||||
Future<core.BuildResult> _runBuilder(core.BuildEnvironment buildEnvironment, BuildOptions buildOptions, List<String> targets, bool release, Set<core.BuildDirectory> buildDirs) async {
|
|
||||||
_builder = await BuildImpl.create(
|
_builder = await BuildImpl.create(
|
||||||
buildOptions,
|
buildOptions,
|
||||||
buildEnvironment,
|
buildEnvironment,
|
||||||
builders,
|
builders,
|
||||||
<String, Map<String, dynamic>>{
|
<String, Map<String, dynamic>>{
|
||||||
'flutter_tools|entrypoint': <String, dynamic>{
|
'flutter_tools:ddc': <String, dynamic>{
|
||||||
'targets': targets,
|
'flutterWebSdk': artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
||||||
|
},
|
||||||
|
'flutter_tools:entrypoint': <String, dynamic>{
|
||||||
|
'release': release,
|
||||||
|
'flutterWebSdk': artifacts.getArtifactPath(Artifact.flutterWebSdk),
|
||||||
|
},
|
||||||
|
'flutter_tools:test_entrypoint': <String, dynamic>{
|
||||||
'release': release,
|
'release': release,
|
||||||
},
|
},
|
||||||
'flutter_tools|test_entrypoint': <String, dynamic>{
|
|
||||||
'targets': targets,
|
|
||||||
'release': release,
|
|
||||||
},
|
|
||||||
'flutter_tools|shell': <String, dynamic>{
|
|
||||||
'targets': targets,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
isReleaseBuild: false,
|
isReleaseBuild: false,
|
||||||
);
|
);
|
||||||
@ -364,301 +197,3 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ddc-only entrypoint builder that respects the Flutter target flag.
|
|
||||||
class FlutterWebTestEntrypointBuilder implements Builder {
|
|
||||||
const FlutterWebTestEntrypointBuilder(this.targets);
|
|
||||||
|
|
||||||
final List<String> targets;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
|
||||||
'.dart': <String>[
|
|
||||||
ddcBootstrapExtension,
|
|
||||||
jsEntrypointExtension,
|
|
||||||
jsEntrypointSourceMapExtension,
|
|
||||||
jsEntrypointArchiveExtension,
|
|
||||||
digestsEntrypointExtension,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> build(BuildStep buildStep) async {
|
|
||||||
bool matches = false;
|
|
||||||
for (String target in targets) {
|
|
||||||
if (buildStep.inputId.path.contains(target)) {
|
|
||||||
matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info('building for target ${buildStep.inputId.path}');
|
|
||||||
await bootstrapDdc(buildStep, platform: flutterWebPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A ddc-only entrypoint builder that respects the Flutter target flag.
|
|
||||||
class FlutterWebEntrypointBuilder implements Builder {
|
|
||||||
const FlutterWebEntrypointBuilder(this.targets, this.release);
|
|
||||||
|
|
||||||
final List<String> targets;
|
|
||||||
final bool release;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
|
||||||
'.dart': <String>[
|
|
||||||
ddcBootstrapExtension,
|
|
||||||
jsEntrypointExtension,
|
|
||||||
jsEntrypointSourceMapExtension,
|
|
||||||
jsEntrypointArchiveExtension,
|
|
||||||
digestsEntrypointExtension,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> build(BuildStep buildStep) async {
|
|
||||||
bool matches = false;
|
|
||||||
for (String target in targets) {
|
|
||||||
if (buildStep.inputId.path.contains(fs.path.setExtension(target, '_web_entrypoint.dart'))) {
|
|
||||||
matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info('building for target ${buildStep.inputId.path}');
|
|
||||||
if (release) {
|
|
||||||
await bootstrapDart2Js(buildStep);
|
|
||||||
} else {
|
|
||||||
await bootstrapDdc(buildStep, platform: flutterWebPlatform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bootstraps the test entrypoint.
|
|
||||||
class FlutterWebTestBootstrapBuilder implements Builder {
|
|
||||||
const FlutterWebTestBootstrapBuilder();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
|
||||||
'_test.dart': <String>[
|
|
||||||
'_test.dart.browser_test.dart',
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> build(BuildStep buildStep) async {
|
|
||||||
final AssetId id = buildStep.inputId;
|
|
||||||
final String contents = await buildStep.readAsString(id);
|
|
||||||
final String assetPath = id.pathSegments.first == 'lib'
|
|
||||||
? path.url.join('packages', id.package, id.path)
|
|
||||||
: id.path;
|
|
||||||
final Metadata metadata = parseMetadata(
|
|
||||||
assetPath, contents, Runtime.builtIn.map((Runtime runtime) => runtime.name).toSet());
|
|
||||||
|
|
||||||
if (metadata.testOn.evaluate(SuitePlatform(Runtime.chrome))) {
|
|
||||||
await buildStep.writeAsString(id.addExtension('.browser_test.dart'), '''
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'dart:html';
|
|
||||||
import 'dart:js';
|
|
||||||
|
|
||||||
import 'package:stream_channel/stream_channel.dart';
|
|
||||||
import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports
|
|
||||||
import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
|
|
||||||
import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports
|
|
||||||
import 'package:test_api/src/suite_channel_manager.dart'; // ignore: implementation_imports
|
|
||||||
|
|
||||||
import "${path.url.basename(id.path)}" as test;
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
// Extra initialization for flutter_web.
|
|
||||||
// The following parameters are hard-coded in Flutter's test embedder. Since
|
|
||||||
// we don't have an embedder yet this is the lowest-most layer we can put
|
|
||||||
// this stuff in.
|
|
||||||
await ui.webOnlyInitializeEngine();
|
|
||||||
// TODO(flutterweb): remove need for dynamic cast.
|
|
||||||
(ui.window as dynamic).debugOverrideDevicePixelRatio(3.0);
|
|
||||||
(ui.window as dynamic).webOnlyDebugPhysicalSizeOverride = const ui.Size(2400, 1800);
|
|
||||||
internalBootstrapBrowserTest(() => test.main);
|
|
||||||
}
|
|
||||||
|
|
||||||
void internalBootstrapBrowserTest(Function getMain()) {
|
|
||||||
var channel =
|
|
||||||
serializeSuite(getMain, hidePrints: false, beforeLoad: () async {
|
|
||||||
var serialized =
|
|
||||||
await suiteChannel("test.browser.mapper").stream.first as Map;
|
|
||||||
if (serialized == null) return;
|
|
||||||
});
|
|
||||||
postMessageChannel().pipe(channel);
|
|
||||||
}
|
|
||||||
StreamChannel serializeSuite(Function getMain(),
|
|
||||||
{bool hidePrints = true, Future beforeLoad()}) =>
|
|
||||||
RemoteListener.start(getMain,
|
|
||||||
hidePrints: hidePrints, beforeLoad: beforeLoad);
|
|
||||||
|
|
||||||
StreamChannel suiteChannel(String name) {
|
|
||||||
var manager = SuiteChannelManager.current;
|
|
||||||
if (manager == null) {
|
|
||||||
throw StateError('suiteChannel() may only be called within a test worker.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return manager.connectOut(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamChannel postMessageChannel() {
|
|
||||||
var controller = StreamChannelController(sync: true);
|
|
||||||
window.onMessage.firstWhere((message) {
|
|
||||||
return message.origin == window.location.origin && message.data == "port";
|
|
||||||
}).then((message) {
|
|
||||||
var port = message.ports.first;
|
|
||||||
var portSubscription = port.onMessage.listen((message) {
|
|
||||||
controller.local.sink.add(message.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
controller.local.stream.listen((data) {
|
|
||||||
port.postMessage({"data": data});
|
|
||||||
}, onDone: () {
|
|
||||||
port.postMessage({"event": "done"});
|
|
||||||
portSubscription.cancel();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context['parent'].callMethod('postMessage', [
|
|
||||||
JsObject.jsify({"href": window.location.href, "ready": true}),
|
|
||||||
window.location.origin,
|
|
||||||
]);
|
|
||||||
return controller.foreign;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStackTraceMapper(StackTraceMapper mapper) {
|
|
||||||
var formatter = StackTraceFormatter.current;
|
|
||||||
if (formatter == null) {
|
|
||||||
throw StateError(
|
|
||||||
'setStackTraceMapper() may only be called within a test worker.');
|
|
||||||
}
|
|
||||||
|
|
||||||
formatter.configure(mapper: mapper);
|
|
||||||
}
|
|
||||||
''');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shell builder which generates the web specific entrypoint.
|
|
||||||
class FlutterWebShellBuilder implements Builder {
|
|
||||||
const FlutterWebShellBuilder(this.targets);
|
|
||||||
|
|
||||||
final List<String> targets;
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<void> build(BuildStep buildStep) async {
|
|
||||||
bool matches = false;
|
|
||||||
for (String target in targets) {
|
|
||||||
if (buildStep.inputId.path.contains(target)) {
|
|
||||||
matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final AssetId outputId = buildStep.inputId.changeExtension('_web_entrypoint.dart');
|
|
||||||
await buildStep.writeAsString(outputId, '''
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import "${path.url.basename(buildStep.inputId.path)}" as entrypoint;
|
|
||||||
|
|
||||||
Future<void> main() async {
|
|
||||||
await ui.webOnlyInitializePlatform();
|
|
||||||
entrypoint.main();
|
|
||||||
}
|
|
||||||
|
|
||||||
''');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
|
||||||
'.dart': <String>['_web_entrypoint.dart'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> bootstrapDart2Js(BuildStep buildStep) 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: false)..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 = await _createPackageFile(allSrcs, buildStep, scratchSpace);
|
|
||||||
final String dartPath = dartEntrypointId.path.startsWith('lib/')
|
|
||||||
? 'package:${dartEntrypointId.package}/'
|
|
||||||
'${dartEntrypointId.path.substring('lib/'.length)}'
|
|
||||||
: dartEntrypointId.path;
|
|
||||||
final String jsOutputPath =
|
|
||||||
'${fs.path.withoutExtension(dartPath.replaceFirst('package:', 'packages/'))}'
|
|
||||||
'$jsEntrypointExtension';
|
|
||||||
final String flutterWebSdkPath = artifacts.getArtifactPath(Artifact.flutterWebSdk);
|
|
||||||
final String librariesPath = fs.path.join(flutterWebSdkPath, 'libraries.json');
|
|
||||||
final List<String> args = <String>[
|
|
||||||
'--libraries-spec="$librariesPath"',
|
|
||||||
'-m',
|
|
||||||
'-o4',
|
|
||||||
'-o',
|
|
||||||
'$jsOutputPath',
|
|
||||||
'--packages="$packageFile"',
|
|
||||||
'-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 io.File jsOutputFile = scratchSpace.fileFor(jsOutputId);
|
|
||||||
if (result.succeeded && jsOutputFile.existsSync()) {
|
|
||||||
log.info(result.output);
|
|
||||||
// Explicitly write out the original js file and sourcemap.
|
|
||||||
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 io.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`.
|
|
||||||
Future<String> _createPackageFile(Iterable<AssetId> inputSources, BuildStep buildStep, ScratchSpace scratchSpace) async {
|
|
||||||
final Uri inputUri = buildStep.inputId.uri;
|
|
||||||
final String packageFileName =
|
|
||||||
'.package-${md5.convert(inputUri.toString().codeUnits)}';
|
|
||||||
final io.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');
|
|
||||||
await packagesFile
|
|
||||||
.writeAsString('# Generated for $inputUri\n$packagesFileContent');
|
|
||||||
return packageFileName;
|
|
||||||
}
|
|
||||||
|
@ -131,8 +131,7 @@ class ResidentWebRunner extends ResidentRunner {
|
|||||||
}
|
}
|
||||||
// Start the web compiler and build the assets.
|
// Start the web compiler and build the assets.
|
||||||
await webCompilationProxy.initialize(
|
await webCompilationProxy.initialize(
|
||||||
projectDirectory: FlutterProject.current().directory,
|
projectDirectory: flutterProject.directory,
|
||||||
targets: <String>[target],
|
|
||||||
);
|
);
|
||||||
_lastCompiled = DateTime.now();
|
_lastCompiled = DateTime.now();
|
||||||
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
|
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
|
||||||
|
@ -76,9 +76,6 @@ Future<int> runTests(
|
|||||||
final bool result = await webCompilationProxy.initialize(
|
final bool result = await webCompilationProxy.initialize(
|
||||||
projectDirectory: flutterProject.directory,
|
projectDirectory: flutterProject.directory,
|
||||||
testOutputDir: tempBuildDir,
|
testOutputDir: tempBuildDir,
|
||||||
targets: testFiles.map((String testFile) {
|
|
||||||
return fs.path.relative(testFile, from: flutterProject.directory.path);
|
|
||||||
}).toList(),
|
|
||||||
);
|
);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throwToolExit('Failed to compile tests');
|
throwToolExit('Failed to compile tests');
|
||||||
|
@ -30,7 +30,6 @@ Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo bu
|
|||||||
try {
|
try {
|
||||||
result = await webCompilationProxy.initialize(
|
result = await webCompilationProxy.initialize(
|
||||||
projectDirectory: FlutterProject.current().directory,
|
projectDirectory: FlutterProject.current().directory,
|
||||||
targets: <String>[target],
|
|
||||||
release: buildInfo.isRelease,
|
release: buildInfo.isRelease,
|
||||||
);
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -79,11 +78,8 @@ class WebCompilationProxy {
|
|||||||
///
|
///
|
||||||
/// `release` controls whether we build the bundle for dartdevc or only
|
/// `release` controls whether we build the bundle for dartdevc or only
|
||||||
/// the entrypoints for dart2js to later take over.
|
/// the entrypoints for dart2js to later take over.
|
||||||
///
|
|
||||||
/// `targets` controls the specific compiler targets.
|
|
||||||
Future<bool> initialize({
|
Future<bool> initialize({
|
||||||
@required Directory projectDirectory,
|
@required Directory projectDirectory,
|
||||||
@required List<String> targets,
|
|
||||||
String testOutputDir,
|
String testOutputDir,
|
||||||
bool release,
|
bool release,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -38,7 +38,6 @@ void main() {
|
|||||||
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
||||||
when(mockWebCompilationProxy.initialize(
|
when(mockWebCompilationProxy.initialize(
|
||||||
projectDirectory: anyNamed('projectDirectory'),
|
projectDirectory: anyNamed('projectDirectory'),
|
||||||
targets: anyNamed('targets'),
|
|
||||||
release: anyNamed('release')
|
release: anyNamed('release')
|
||||||
)).thenAnswer((Invocation invocation) {
|
)).thenAnswer((Invocation invocation) {
|
||||||
final String path = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib', 'main_web_entrypoint.dart.js');
|
final String path = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib', 'main_web_entrypoint.dart.js');
|
||||||
|
Loading…
Reference in New Issue
Block a user