| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:typed_data'; |
| |
| import 'package:archive/archive.dart'; |
| import 'package:build_daemon/client.dart'; |
| import 'package:build_daemon/constants.dart' as daemon; |
| import 'package:build_daemon/data/build_status.dart'; |
| import 'package:build_daemon/data/build_target.dart'; |
| import 'package:build_daemon/data/server_log.dart'; |
| import 'package:dwds/asset_handler.dart'; |
| import 'package:dwds/dwds.dart'; |
| import 'package:http_multi_server/http_multi_server.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:shelf/shelf.dart'; |
| import 'package:shelf/shelf_io.dart' as shelf_io; |
| import 'package:shelf_proxy/shelf_proxy.dart'; |
| import 'package:mime/mime.dart' as mime; |
| |
| import '../artifacts.dart'; |
| import '../asset.dart'; |
| import '../base/common.dart'; |
| import '../base/context.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/net.dart'; |
| import '../base/os.dart'; |
| import '../base/platform.dart'; |
| import '../build_info.dart'; |
| import '../bundle.dart'; |
| import '../cache.dart'; |
| import '../dart/package_map.dart'; |
| import '../dart/pub.dart'; |
| import '../globals.dart'; |
| 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'; |
| |
| /// A factory for creating a [Dwds] instance. |
| DwdsFactory get dwdsFactory => context.get<DwdsFactory>() ?? Dwds.start; |
| |
| /// The [BuildDaemonCreator] instance. |
| BuildDaemonCreator get buildDaemonCreator => context.get<BuildDaemonCreator>() ?? const BuildDaemonCreator(); |
| |
| /// A factory for creating a [WebFs] instance. |
| WebFsFactory get webFsFactory => context.get<WebFsFactory>() ?? WebFs.start; |
| |
| /// A factory for creating an [HttpMultiServer] instance. |
| HttpMultiServerFactory get httpMultiServerFactory => context.get<HttpMultiServerFactory>() ?? HttpMultiServer.bind; |
| |
| /// A function with the same signature as [HttpMultiServer.bind]. |
| typedef HttpMultiServerFactory = Future<HttpServer> Function(dynamic address, int port); |
| |
| /// A function with the same signature as [Dwds.start]. |
| typedef DwdsFactory = Future<Dwds> Function({ |
| @required AssetHandler assetHandler, |
| @required Stream<BuildResult> buildResults, |
| @required ConnectionProvider chromeConnection, |
| String hostname, |
| ReloadConfiguration reloadConfiguration, |
| bool serveDevTools, |
| LogWriter logWriter, |
| bool verbose, |
| bool enableDebugExtension, |
| UrlEncoder urlEncoder, |
| }); |
| |
| /// A function with the same signature as [WebFs.start]. |
| typedef WebFsFactory = Future<WebFs> Function({ |
| @required String target, |
| @required FlutterProject flutterProject, |
| @required BuildInfo buildInfo, |
| @required bool skipDwds, |
| @required bool initializePlatform, |
| @required String hostname, |
| @required String port, |
| @required UrlTunneller urlTunneller, |
| @required List<String> dartDefines, |
| }); |
| |
| /// The dev filesystem responsible for building and serving web applications. |
| class WebFs { |
| @visibleForTesting |
| WebFs( |
| this._client, |
| this._server, |
| this._dwds, |
| this.uri, |
| this._assetServer, |
| this._useBuildRunner, |
| this._flutterProject, |
| this._target, |
| this._buildInfo, |
| this._initializePlatform, |
| this._dartDefines, |
| ); |
| |
| /// The server URL. |
| final String uri; |
| |
| final HttpServer _server; |
| final Dwds _dwds; |
| final BuildDaemonClient _client; |
| final AssetServer _assetServer; |
| final bool _useBuildRunner; |
| final FlutterProject _flutterProject; |
| final String _target; |
| final BuildInfo _buildInfo; |
| final bool _initializePlatform; |
| final List<String> _dartDefines; |
| StreamSubscription<void> _connectedApps; |
| |
| static const String _kHostName = 'localhost'; |
| |
| Future<void> stop() async { |
| await _client?.close(); |
| await _dwds?.stop(); |
| await _server.close(force: true); |
| await _connectedApps?.cancel(); |
| _assetServer?.dispose(); |
| } |
| |
| Future<DebugConnection> _cachedExtensionFuture; |
| |
| /// Connect and retrieve the [DebugConnection] for the current application. |
| /// |
| /// Only calls [AppConnection.runMain] on the subsequent connections. |
| Future<ConnectionResult> connect(bool useDebugExtension) { |
| final Completer<ConnectionResult> firstConnection = Completer<ConnectionResult>(); |
| _connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async { |
| final DebugConnection debugConnection = useDebugExtension |
| ? await (_cachedExtensionFuture ??= _dwds.extensionDebugConnections.stream.first) |
| : await _dwds.debugConnection(appConnection); |
| if (!firstConnection.isCompleted) { |
| firstConnection.complete(ConnectionResult(appConnection, debugConnection)); |
| } else { |
| appConnection.runMain(); |
| } |
| }); |
| return firstConnection.future; |
| } |
| |
| /// Recompile the web application and return whether this was successful. |
| Future<bool> recompile() async { |
| if (!_useBuildRunner) { |
| await buildWeb(_flutterProject, _target, _buildInfo, _initializePlatform, _dartDefines); |
| return true; |
| } |
| _client.startBuild(); |
| await for (BuildResults results in _client.buildResults) { |
| final BuildResult result = results.results.firstWhere((BuildResult result) { |
| return result.target == kBuildTargetName; |
| }); |
| if (result.status == BuildStatus.failed) { |
| return false; |
| } |
| if (result.status == BuildStatus.succeeded) { |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| /// Start the web compiler and asset server. |
| static Future<WebFs> start({ |
| @required String target, |
| @required FlutterProject flutterProject, |
| @required BuildInfo buildInfo, |
| @required bool skipDwds, |
| @required bool initializePlatform, |
| @required String hostname, |
| @required String port, |
| @required UrlTunneller urlTunneller, |
| @required List<String> dartDefines, |
| }) async { |
| // workaround for https://github.com/flutter/flutter/issues/38290 |
| if (!flutterProject.dartTool.existsSync()) { |
| flutterProject.dartTool.createSync(recursive: true); |
| } |
| // Workaround for https://github.com/flutter/flutter/issues/41681. |
| final String toolPath = fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'); |
| if (!fs.isFileSync(fs.path.join(toolPath, '.packages'))) { |
| await pub.get( |
| context: PubContext.pubGet, |
| directory: toolPath, |
| offline: true, |
| skipPubspecYamlCheck: true, |
| checkLastModified: false, |
| ); |
| } |
| |
| final Completer<bool> firstBuildCompleter = Completer<bool>(); |
| |
| // Initialize the asset bundle. |
| final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); |
| await assetBundle.build(); |
| await writeBundle(fs.directory(getAssetBuildDirectory()), assetBundle.entries); |
| |
| final String targetBaseName = fs.path |
| .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', |
| '${targetBaseName}_web_entrypoint.dart.js.map': 'packages/${flutterProject.manifest.appName}/' |
| '${targetBaseName}_web_entrypoint.dart.js.map', |
| '${targetBaseName}_web_entrypoint.dart.bootstrap.js': 'packages/${flutterProject.manifest.appName}/' |
| '${targetBaseName}_web_entrypoint.dart.bootstrap.js', |
| '${targetBaseName}_web_entrypoint.digests': 'packages/${flutterProject.manifest.appName}/' |
| '${targetBaseName}_web_entrypoint.digests', |
| }; |
| |
| // Initialize the dwds server. |
| final String effectiveHostname = hostname ?? _kHostName; |
| final int hostPort = port == null ? await os.findFreePort() : int.tryParse(port); |
| |
| final Pipeline pipeline = const Pipeline().addMiddleware((Handler innerHandler) { |
| return (Request request) async { |
| // Redirect the main.dart.js to the target file we decided to serve. |
| if (mappedUrls.containsKey(request.url.path)) { |
| final String newPath = mappedUrls[request.url.path]; |
| return innerHandler( |
| Request( |
| request.method, |
| Uri.parse(request.requestedUri.toString() |
| .replaceFirst(request.requestedUri.path, '/$newPath')), |
| headers: request.headers, |
| url: Uri.parse(request.url.toString() |
| .replaceFirst(request.url.path, newPath)), |
| ), |
| ); |
| } else { |
| return innerHandler(request); |
| } |
| }; |
| }); |
| |
| Handler handler; |
| Dwds dwds; |
| 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) { |
| final BuildRunnerAssetHandler assetHandler = BuildRunnerAssetHandler( |
| daemonAssetPort, |
| kBuildTargetName, |
| effectiveHostname, |
| hostPort); |
| dwds = await dwdsFactory( |
| hostname: effectiveHostname, |
| assetHandler: assetHandler, |
| buildResults: filteredBuildResults, |
| chromeConnection: () async { |
| return (await ChromeLauncher.connectedInstance).chromeConnection; |
| }, |
| reloadConfiguration: ReloadConfiguration.none, |
| serveDevTools: false, |
| verbose: false, |
| enableDebugExtension: true, |
| urlEncoder: urlTunneller, |
| logWriter: (dynamic level, String message) => printTrace(message), |
| ); |
| handler = pipeline.addHandler(dwds.handler); |
| } else { |
| handler = pipeline.addHandler(proxyHandler('http://localhost:$daemonAssetPort/web/')); |
| } |
| } else { |
| await buildWeb(flutterProject, target, buildInfo, initializePlatform, dartDefines); |
| firstBuildCompleter.complete(true); |
| } |
| |
| final AssetServer assetServer = buildInfo.isDebug |
| ? DebugAssetServer(flutterProject, targetBaseName) |
| : ReleaseAssetServer(); |
| Cascade cascade = Cascade(); |
| cascade = cascade.add(handler); |
| cascade = cascade.add(assetServer.handle); |
| final HttpServer server = await httpMultiServerFactory(effectiveHostname, hostPort); |
| shelf_io.serveRequests(server, cascade.handler); |
| final WebFs webFS = WebFs( |
| client, |
| server, |
| dwds, |
| 'http://$effectiveHostname:$hostPort/', |
| assetServer, |
| buildInfo.isDebug, |
| flutterProject, |
| target, |
| buildInfo, |
| initializePlatform, |
| dartDefines, |
| ); |
| if (!await firstBuildCompleter.future) { |
| throw const BuildException(); |
| } |
| await firstBuild?.cancel(); |
| return webFS; |
| } |
| } |
| |
| /// An exception thrown when build runner fails. |
| /// |
| /// This contains no error information as it will have already been printed to |
| /// the console. |
| class BuildException implements Exception { |
| const BuildException(); |
| } |
| |
| abstract class AssetServer { |
| Future<Response> handle(Request request); |
| |
| void dispose() {} |
| } |
| |
| class ReleaseAssetServer extends AssetServer { |
| // Locations where source files, assets, or source maps may be located. |
| final List<Uri> _searchPaths = <Uri>[ |
| fs.directory(getWebBuildDirectory()).uri, |
| fs.directory(Cache.flutterRoot).parent.uri, |
| fs.currentDirectory.childDirectory('lib').uri, |
| ]; |
| |
| @override |
| Future<Response> handle(Request request) async { |
| Uri fileUri; |
| for (Uri uri in _searchPaths) { |
| final Uri potential = uri.resolve(request.url.path); |
| if (potential == null || !fs.isFileSync(potential.toFilePath())) { |
| continue; |
| } |
| fileUri = potential; |
| break; |
| } |
| |
| if (fileUri != null) { |
| final File file = fs.file(fileUri); |
| final Uint8List bytes = file.readAsBytesSync(); |
| // Fallback to "application/octet-stream" on null which |
| // makes no claims as to the structure of the data. |
| final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) |
| ?? 'application/octet-stream'; |
| return Response.ok(bytes, headers: <String, String>{ |
| 'Content-Type': mimeType, |
| }); |
| } |
| 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': 'text/html', |
| }); |
| } |
| return Response.notFound(''); |
| } |
| } |
| |
| 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); |
| final File htmlFile = fs.file(htmlUri); |
| if (htmlFile.existsSync()) { |
| return Response.ok(htmlFile.readAsBytesSync(), headers: <String, String>{ |
| 'Content-Type': 'text/html', |
| }); |
| } |
| return Response.notFound(''); |
| } else if (request.url.path.contains('stack_trace_mapper')) { |
| final File file = fs.file(fs.path.join( |
| artifacts.getArtifactPath(Artifact.engineDartSdkPath), |
| 'lib', |
| 'dev_compiler', |
| 'web', |
| 'dart_stack_trace_mapper.js', |
| )); |
| return Response.ok(file.readAsBytesSync(), headers: <String, String>{ |
| 'Content-Type': 'text/javascript', |
| }); |
| } else if (request.url.path.endsWith('part.js')) { |
| // Lazily unpack any deferred imports in release/profile mode. These are |
| // placed into an archive by build_runner, and are named based on the main |
| // entrypoint + a "part" suffix (Though the actual names are arbitrary). |
| // To make this easier to deal with they are copied into a temp directory. |
| if (partFiles == null) { |
| final File dart2jsArchive = fs.file(fs.path.join( |
| flutterProject.dartTool.path, |
| 'build', |
| 'flutter_web', |
| '${flutterProject.manifest.appName}', |
| 'lib', |
| '${targetBaseName}_web_entrypoint.dart.js.tar.gz', |
| )); |
| if (dart2jsArchive.existsSync()) { |
| final Archive archive = TarDecoder().decodeBytes(dart2jsArchive.readAsBytesSync()); |
| partFiles = fs.systemTempDirectory.createTempSync('flutter_tool.') |
| ..createSync(); |
| for (ArchiveFile file in archive) { |
| partFiles.childFile(file.name).writeAsBytesSync(file.content as List<int>); |
| } |
| } |
| } |
| final String fileName = fs.path.basename(request.url.path); |
| return Response.ok(partFiles.childFile(fileName).readAsBytesSync(), headers: <String, String>{ |
| 'Content-Type': 'text/javascript', |
| }); |
| } else if (request.url.path.contains('require.js')) { |
| final File file = fs.file(fs.path.join( |
| artifacts.getArtifactPath(Artifact.engineDartSdkPath), |
| 'lib', |
| 'dev_compiler', |
| 'kernel', |
| 'amd', |
| 'require.js', |
| )); |
| return Response.ok(file.readAsBytesSync(), headers: <String, String>{ |
| 'Content-Type': 'text/javascript', |
| }); |
| } else if (request.url.path.endsWith('dart_sdk.js.map')) { |
| final File file = fs.file(fs.path.join( |
| artifacts.getArtifactPath(Artifact.flutterWebSdk), |
| 'kernel', |
| 'amd', |
| 'dart_sdk.js.map', |
| )); |
| return Response.ok(file.readAsBytesSync()); |
| } else if (request.url.path.endsWith('dart_sdk.js')) { |
| final File file = fs.file(fs.path.join( |
| artifacts.getArtifactPath(Artifact.flutterWebSdk), |
| 'kernel', |
| 'amd', |
| 'dart_sdk.js', |
| )); |
| return Response.ok(file.readAsBytesSync(), headers: <String, String>{ |
| 'Content-Type': 'text/javascript', |
| }); |
| } else if (request.url.path.endsWith('.dart')) { |
| // This is likely a sourcemap request. The first segment is the |
| // package name, and the rest is the path to the file relative to |
| // the package uri. For example, `foo/bar.dart` would represent a |
| // file at a path like `foo/lib/bar.dart`. If there is no leading |
| // segment, then we assume it is from the current package. |
| String basePath = request.url.path; |
| basePath = basePath.replaceFirst('packages/build_web_compilers/', ''); |
| basePath = basePath.replaceFirst('packages/', ''); |
| |
| // Handle sdk requests that have mangled urls from engine build. |
| if (request.url.path.contains('dart-sdk')) { |
| // Note: the request is a uri and not a file path, so they always use `/`. |
| final String sdkPath = fs.path.joinAll(request.url.path.split('dart-sdk/').last.split('/')); |
| final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath); |
| final File candidateFile = fs.file(fs.path.join(dartSdkPath, sdkPath)); |
| return Response.ok(candidateFile.readAsBytesSync()); |
| } |
| |
| // See if it is a flutter sdk path. |
| final String webSdkPath = artifacts.getArtifactPath(Artifact.flutterWebSdk); |
| final File candidateFile = fs.file(fs.path.join(webSdkPath, |
| basePath.split('/').join(platform.pathSeparator))); |
| if (candidateFile.existsSync()) { |
| return Response.ok(candidateFile.readAsBytesSync()); |
| } |
| |
| final String packageName = request.url.pathSegments.length == 1 |
| ? flutterProject.manifest.appName |
| : request.url.pathSegments.first; |
| String filePath = fs.path.joinAll(request.url.pathSegments.length == 1 |
| ? request.url.pathSegments |
| : request.url.pathSegments.skip(1)); |
| String packagePath = packageMap.map[packageName]?.toFilePath(windows: platform.isWindows); |
| // If the package isn't found, then we have an issue with relative |
| // paths within the main project. |
| if (packagePath == null) { |
| packagePath = packageMap.map[flutterProject.manifest.appName] |
| .toFilePath(windows: platform.isWindows); |
| filePath = request.url.path; |
| } |
| final File file = fs.file(fs.path.join(packagePath, filePath)); |
| if (file.existsSync()) { |
| return Response.ok(file.readAsBytesSync()); |
| } |
| return Response.notFound(''); |
| } else if (request.url.path.contains('assets')) { |
| final String assetPath = request.url.path.replaceFirst('assets/', ''); |
| final File file = fs.file(fs.path.join(getAssetBuildDirectory(), assetPath)); |
| if (file.existsSync()) { |
| final Uint8List bytes = file.readAsBytesSync(); |
| // Fallback to "application/octet-stream" on null which |
| // makes no claims as to the structure of the data. |
| final String mimeType = mime.lookupMimeType(file.path, headerBytes: bytes) |
| ?? 'application/octet-stream'; |
| return Response.ok(bytes, headers: <String, String>{ |
| 'Content-Type': mimeType, |
| }); |
| } else { |
| return Response.notFound(''); |
| } |
| } |
| return Response.notFound(''); |
| } |
| |
| @override |
| void dispose() { |
| partFiles?.deleteSync(recursive: true); |
| } |
| } |
| |
| class ConnectionResult { |
| ConnectionResult(this.appConnection, this.debugConnection); |
| |
| final AppConnection appConnection; |
| final DebugConnection debugConnection; |
| } |
| |
| class WebTestTargetManifest { |
| WebTestTargetManifest(this.buildFilters); |
| |
| WebTestTargetManifest.all() : buildFilters = null; |
| |
| final List<String> buildFilters; |
| |
| bool get hasBuildFilters => buildFilters != null && buildFilters.isNotEmpty; |
| } |
| |
| /// A testable interface for starting a build daemon. |
| class BuildDaemonCreator { |
| const BuildDaemonCreator(); |
| |
| // TODO(jonahwilliams): find a way to get build checks working for flutter for web. |
| static const String _ignoredLine1 = 'Warning: Interpreting this as package URI'; |
| static const String _ignoredLine2 = 'build_script.dart was not found in the asset graph, incremental builds will not work'; |
| static const String _ignoredLine3 = 'have your dependencies specified fully in your pubspec.yaml'; |
| |
| /// Start a build daemon and register the web targets. |
| /// |
| /// [initializePlatform] controls whether we should invoke [webOnlyInitializePlatform]. |
| Future<BuildDaemonClient> startBuildDaemon(String workingDirectory, { |
| bool release = false, |
| bool profile = false, |
| bool hasPlugins = false, |
| bool initializePlatform = true, |
| WebTestTargetManifest testTargets, |
| }) async { |
| try { |
| final BuildDaemonClient client = await _connectClient( |
| workingDirectory, |
| release: release, |
| profile: profile, |
| hasPlugins: hasPlugins, |
| initializePlatform: initializePlatform, |
| testTargets: testTargets, |
| ); |
| _registerBuildTargets(client, testTargets); |
| return client; |
| } on OptionsSkew { |
| throwToolExit( |
| 'Incompatible options with current running build daemon.\n\n' |
| 'Please stop other flutter_tool instances running in this directory ' |
| 'before starting a new instance with these options.'); |
| } |
| return null; |
| } |
| |
| void _registerBuildTargets( |
| BuildDaemonClient client, |
| WebTestTargetManifest testTargets, |
| ) { |
| final OutputLocation outputLocation = OutputLocation((OutputLocationBuilder b) => b |
| ..output = '' |
| ..useSymlinks = true |
| ..hoist = false); |
| client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) => b |
| ..target = 'web' |
| ..outputLocation = outputLocation?.toBuilder())); |
| if (testTargets != null) { |
| client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) { |
| b.target = 'test'; |
| b.outputLocation = outputLocation?.toBuilder(); |
| if (testTargets.hasBuildFilters) { |
| b.buildFilters.addAll(testTargets.buildFilters); |
| } |
| return b; |
| })); |
| } |
| } |
| |
| Future<BuildDaemonClient> _connectClient( |
| String workingDirectory, { |
| bool release, |
| bool profile, |
| bool hasPlugins, |
| bool initializePlatform, |
| WebTestTargetManifest testTargets, |
| }) { |
| final String flutterToolsPackages = fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools', '.packages'); |
| final String buildScript = fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools', 'lib', 'src', 'build_runner', 'build_script.dart'); |
| final String flutterWebSdk = artifacts.getArtifactPath(Artifact.flutterWebSdk); |
| |
| // On Windows we need to call the snapshot directly otherwise |
| // the process will start in a disjoint cmd without access to |
| // STDIO. |
| final List<String> args = <String>[ |
| artifacts.getArtifactPath(Artifact.engineDartBinary), |
| '--packages=$flutterToolsPackages', |
| buildScript, |
| 'daemon', |
| '--skip-build-script-check', |
| '--define', 'flutter_tools:ddc=flutterWebSdk=$flutterWebSdk', |
| '--define', 'flutter_tools:entrypoint=flutterWebSdk=$flutterWebSdk', |
| '--define', 'flutter_tools:entrypoint=release=$release', |
| '--define', 'flutter_tools:entrypoint=profile=$profile', |
| '--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk', |
| '--define', 'flutter_tools:shell=hasPlugins=$hasPlugins', |
| '--define', 'flutter_tools:shell=initializePlatform=$initializePlatform', |
| // The following will cause build runner to only build tests that were requested. |
| if (testTargets != null && testTargets.hasBuildFilters) |
| for (String buildFilter in testTargets.buildFilters) |
| '--build-filter=$buildFilter', |
| ]; |
| |
| return BuildDaemonClient.connect( |
| workingDirectory, |
| args, |
| logHandler: (ServerLog serverLog) { |
| switch (serverLog.level) { |
| case Level.SEVERE: |
| case Level.SHOUT: |
| // Ignore certain non-actionable messages on startup. |
| if (serverLog.message.contains(_ignoredLine1) || |
| serverLog.message.contains(_ignoredLine2) || |
| serverLog.message.contains(_ignoredLine3)) { |
| return; |
| } |
| printError(serverLog.message); |
| if (serverLog.error != null) { |
| printError(serverLog.error); |
| } |
| if (serverLog.stackTrace != null) { |
| printTrace(serverLog.stackTrace); |
| } |
| break; |
| default: |
| if (serverLog.message.contains('Skipping compiling')) { |
| printError(serverLog.message); |
| } else { |
| printTrace(serverLog.message); |
| } |
| } |
| }, |
| buildMode: daemon.BuildMode.Manual, |
| ); |
| } |
| |
| /// Retrieve the asset server port for the current daemon. |
| int assetServerPort(Directory workingDirectory) { |
| final String portFilePath = fs.path.join(daemon.daemonWorkspace(workingDirectory.path), '.asset_server_port'); |
| return int.tryParse(fs.file(portFilePath).readAsStringSync()); |
| } |
| } |