blob: 5e226fa634a8a6a4d5beced81b737725fb157dbf [file] [log] [blame]
// 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 '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',
'--enable-experiment=non-nullable',
'--skip-build-script-check',
'--define', 'flutter_tools:ddc=flutterWebSdk=$flutterWebSdk',
// 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,
);
}
}