blob: 233497aa3ef696ea5e24e13687d112c1c816c22e [file] [log] [blame]
// 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 'package:build/build.dart';
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/node.dart';
import 'package:build_runner_core/src/generate/build_impl.dart';
import 'package:build_runner_core/src/generate/options.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:watcher/watcher.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../compile.dart';
import '../dart/package_map.dart';
import '../globals.dart';
import '../web/compile.dart';
import 'build_script.dart';
/// A build_runner specific implementation of the [WebCompilationProxy].
class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
BuildRunnerWebCompilationProxy();
core.PackageGraph _packageGraph;
BuildImpl _builder;
PackageUriMapper _packageUriMapper;
@override
Future<bool> initialize({
Directory projectDirectory,
String testOutputDir,
bool release = false,
}) async {
// Create the .dart_tool directory if it doesn't exist.
projectDirectory.childDirectory('.dart_tool').createSync();
// Override the generated output directory so this does not conflict with
// other build_runner output.
core.overrideGeneratedOutputDirectory('flutter_web');
_packageUriMapper = PackageUriMapper(
path.absolute('lib/main.dart'), PackageMap.globalPackagesPath, null, null);
_packageGraph = core.PackageGraph.forPath(projectDirectory.path);
final core.BuildEnvironment buildEnvironment = core.OverrideableEnvironment(
core.IOEnvironment(_packageGraph), onLog: (LogRecord record) {
if (record.level == Level.SEVERE || record.level == Level.SHOUT) {
printError(record.message);
} else {
printTrace(record.message);
}
});
final LogSubscription logSubscription = LogSubscription(
buildEnvironment,
verbose: false,
logLevel: Level.FINE,
);
final BuildOptions buildOptions = await BuildOptions.create(
logSubscription,
packageGraph: _packageGraph,
skipBuildScriptCheck: true,
trackPerformance: false,
deleteFilesByDefault: true,
enableLowResourcesMode: platform.environment['FLUTTER_LOW_RESOURCE_MODE']?.toLowerCase() == 'true',
);
final Set<core.BuildDirectory> buildDirs = <core.BuildDirectory>{
if (testOutputDir != null)
core.BuildDirectory(
'test',
outputLocation: core.OutputLocation(
testOutputDir,
useSymlinks: !platform.isWindows,
),
),
};
core.BuildResult result;
try {
result = await _runBuilder(
buildEnvironment,
buildOptions,
release,
buildDirs,
);
return result.status == core.BuildStatus.success;
} on core.BuildConfigChangedException {
await _cleanAssets(projectDirectory);
result = await _runBuilder(
buildEnvironment,
buildOptions,
release,
buildDirs,
);
return result.status == core.BuildStatus.success;
} on core.BuildScriptChangedException {
await _cleanAssets(projectDirectory);
result = await _runBuilder(
buildEnvironment,
buildOptions,
release,
buildDirs,
);
return result.status == core.BuildStatus.success;
}
}
@override
Future<bool> invalidate({@required List<Uri> inputs}) async {
final Status status =
logger.startProgress('Recompiling sources...', timeout: null);
final Map<AssetId, ChangeType> updates = <AssetId, ChangeType>{};
for (Uri input in inputs) {
final AssetId assetId = AssetId.resolve(_packageUriMapper.map(input.toFilePath()).toString());
updates[assetId] = ChangeType.MODIFY;
}
core.BuildResult result;
try {
result = await _builder.run(updates);
} finally {
status.cancel();
}
return result.status == core.BuildStatus.success;
}
Future<core.BuildResult> _runBuilder(core.BuildEnvironment buildEnvironment, BuildOptions buildOptions, bool release, Set<core.BuildDirectory> buildDirs) async {
_builder = await BuildImpl.create(
buildOptions,
buildEnvironment,
builders,
<String, Map<String, dynamic>>{
'flutter_tools:ddc': <String, dynamic>{
'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,
},
},
isReleaseBuild: false,
);
return _builder.run(
const <AssetId, ChangeType>{},
buildDirs: buildDirs,
);
}
Future<void> _cleanAssets(Directory projectDirectory) async {
final File assetGraphFile = fs.file(core.assetGraphPath);
AssetGraph assetGraph;
try {
assetGraph = AssetGraph.deserialize(await assetGraphFile.readAsBytes());
} catch (_) {
printTrace('Failed to clean up asset graph.');
}
final core.PackageGraph packageGraph = core.PackageGraph.forThisPackage();
await _cleanUpSourceOutputs(assetGraph, packageGraph);
final Directory cacheDirectory = fs.directory(fs.path.join(
projectDirectory.path,
'.dart_tool',
'build',
'flutter_web',
));
if (assetGraphFile.existsSync()) {
assetGraphFile.deleteSync();
}
if (cacheDirectory.existsSync()) {
cacheDirectory.deleteSync(recursive: true);
}
}
Future<void> _cleanUpSourceOutputs(AssetGraph assetGraph, core.PackageGraph packageGraph) async {
final core.FileBasedAssetWriter writer = core.FileBasedAssetWriter(packageGraph);
if (assetGraph?.outputs == null) {
return;
}
for (AssetId id in assetGraph.outputs) {
if (id.package != packageGraph.root.name) {
continue;
}
final GeneratedAssetNode node = assetGraph.get(id);
if (node.wasOutput) {
// Note that this does a file.exists check in the root package and
// only tries to delete the file if it exists. This way we only
// actually delete to_source outputs, without reading in the build
// actions.
await writer.delete(id);
}
}
}
}