| // 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 '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:path/path.dart' as path; // ignore: package_path_import |
| |
| import '../artifacts.dart'; |
| import '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../build_info.dart'; |
| import '../cache.dart'; |
| import '../dart/pub.dart'; |
| import '../globals.dart' as globals; |
| import '../platform_plugins.dart'; |
| import '../plugins.dart'; |
| import '../project.dart'; |
| import '../web/compile.dart'; |
| |
| /// A build_runner specific implementation of the [WebCompilationProxy]. |
| class BuildRunnerWebCompilationProxy extends WebCompilationProxy { |
| BuildRunnerWebCompilationProxy(); |
| |
| @override |
| Future<bool> initialize({ |
| Directory projectDirectory, |
| String testOutputDir, |
| List<String> testFiles, |
| BuildMode mode, |
| String projectName, |
| bool initializePlatform, |
| }) async { |
| // Create the .dart_tool directory if it doesn't exist. |
| projectDirectory |
| .childDirectory('.dart_tool') |
| .createSync(); |
| final FlutterProject flutterProject = FlutterProject.fromDirectory(projectDirectory); |
| final bool hasWebPlugins = (await findPlugins(flutterProject)) |
| .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey)); |
| final BuildDaemonClient client = await const BuildDaemonCreator().startBuildDaemon( |
| projectDirectory.path, |
| release: mode == BuildMode.release, |
| profile: mode == BuildMode.profile, |
| hasPlugins: hasWebPlugins, |
| initializePlatform: initializePlatform, |
| testTargets: WebTestTargetManifest( |
| testFiles |
| .map<String>((String absolutePath) { |
| final String relativePath = path.relative(absolutePath, from: projectDirectory.path); |
| return '${path.withoutExtension(relativePath)}.*'; |
| }) |
| .toList(), |
| ), |
| ); |
| client.startBuild(); |
| bool success = true; |
| await for (final BuildResults results in client.buildResults) { |
| final BuildResult result = results.results.firstWhere((BuildResult result) { |
| return result.target == 'web'; |
| }, orElse: () { |
| // Assume build failed if we lack any results. |
| return DefaultBuildResult((DefaultBuildResultBuilder b) => b.status == BuildStatus.failed); |
| }); |
| if (result.status == BuildStatus.failed) { |
| success = false; |
| break; |
| } |
| if (result.status == BuildStatus.succeeded) { |
| break; |
| } |
| } |
| if (!success || testOutputDir == null) { |
| return success; |
| } |
| final Directory rootDirectory = projectDirectory |
| .childDirectory('.dart_tool') |
| .childDirectory('build') |
| .childDirectory('flutter_web'); |
| |
| final Iterable<Directory> childDirectories = rootDirectory |
| .listSync() |
| .whereType<Directory>(); |
| for (final Directory childDirectory in childDirectories) { |
| final String path = globals.fs.path.join( |
| testOutputDir, |
| 'packages', |
| globals.fs.path.basename(childDirectory.path), |
| ); |
| globals.fsUtils.copyDirectorySync( |
| childDirectory.childDirectory('lib'), |
| globals.fs.directory(path), |
| ); |
| } |
| final Directory outputDirectory = rootDirectory |
| .childDirectory(projectName) |
| .childDirectory('test'); |
| globals.fsUtils.copyDirectorySync( |
| outputDirectory, |
| globals.fs.directory(globals.fs.path.join(testOutputDir)), |
| ); |
| return success; |
| } |
| } |
| |
| 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); |
| } |
| })); |
| } |
| } |
| |
| Future<BuildDaemonClient> _connectClient( |
| String workingDirectory, { |
| bool release, |
| bool profile, |
| bool hasPlugins, |
| bool initializePlatform, |
| WebTestTargetManifest testTargets, |
| }) async { |
| // The build script is stored in an auxiliary package to reduce |
| // dependencies of the main tool. |
| final String buildScriptPackages = globals.fs.path.join( |
| Cache.flutterRoot, |
| 'packages', |
| '_flutter_web_build_script', |
| '.packages', |
| ); |
| final String buildScript = globals.fs.path.join( |
| Cache.flutterRoot, |
| 'packages', |
| '_flutter_web_build_script', |
| 'lib', |
| 'build_script.dart', |
| ); |
| if (!globals.fs.isFileSync(buildScript)) { |
| throwToolExit('Expected a file $buildScript to exist in the Flutter SDK.'); |
| } |
| // If we're missing the .packages file, perform a pub get. |
| if (!globals.fs.isFileSync(buildScriptPackages)) { |
| await pub.get( |
| context: PubContext.pubGet, |
| directory: globals.fs.file(buildScriptPackages).parent.path, |
| generateSyntheticPackage: false, |
| ); |
| } |
| final String flutterWebSdk = globals.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>[ |
| globals.artifacts.getArtifactPath(Artifact.engineDartBinary), |
| '--disable-dart-dev', |
| '--packages=$buildScriptPackages', |
| 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 (final 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; |
| } |
| globals.printError(serverLog.message); |
| if (serverLog.error != null) { |
| globals.printError(serverLog.error); |
| } |
| if (serverLog.stackTrace != null) { |
| globals.printTrace(serverLog.stackTrace); |
| } |
| break; |
| default: |
| if (serverLog.message.contains('Skipping compiling')) { |
| globals.printError(serverLog.message); |
| } else { |
| globals.printTrace(serverLog.message); |
| } |
| } |
| }, |
| buildMode: daemon.BuildMode.Manual, |
| ); |
| } |
| } |