blob: 9aa8078c10d926db3afbf9d597cd92f10ae04b18 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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:collection';
import 'dart:convert';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:async/async.dart';
import 'package:crypto/crypto.dart';
import 'package:glob/glob.dart';
import 'package:package_config/package_config_types.dart';
import '../analyzer/resolver.dart';
import '../asset/exceptions.dart';
import '../asset/id.dart';
import '../asset/reader.dart';
import '../asset/writer.dart';
import '../resource/resource.dart';
import 'build_step.dart';
import 'exceptions.dart';
/// A single step in the build processes.
///
/// This represents a single input and its expected and real outputs. It also
/// handles tracking of dependencies.
class BuildStepImpl implements BuildStep {
final Resolvers? _resolvers;
final StageTracker _stageTracker;
/// The primary input id for this build step.
@override
final AssetId inputId;
@override
Future<LibraryElement> get inputLibrary async {
if (_isComplete) throw BuildStepCompletedException();
return resolver.libraryFor(inputId);
}
/// The list of all outputs which are expected/allowed to be output from this
/// step.
@override
final Set<AssetId> allowedOutputs;
/// The result of any writes which are starting during this step.
final _writeResults = <Future<Result<void>>>[];
final _writtenAssets = <AssetId>{};
/// Used internally for reading files.
final AssetReader _reader;
/// Used internally for writing files.
final AssetWriter _writer;
final ResourceManager _resourceManager;
bool _isComplete = false;
final void Function(Iterable<AssetId>)? _reportUnusedAssets;
final Future<PackageConfig> Function() _resolvePackageConfig;
Future<Result<PackageConfig>>? _resolvedPackageConfig;
BuildStepImpl(
this.inputId,
Iterable<AssetId> expectedOutputs,
this._reader,
this._writer,
this._resolvers,
this._resourceManager,
this._resolvePackageConfig,
{StageTracker? stageTracker,
void Function(Iterable<AssetId>)? reportUnusedAssets})
: allowedOutputs = UnmodifiableSetView(expectedOutputs.toSet()),
_stageTracker = stageTracker ?? NoOpStageTracker.instance,
_reportUnusedAssets = reportUnusedAssets;
@override
Future<PackageConfig> get packageConfig async {
final resolved =
_resolvedPackageConfig ??= Result.capture(_resolvePackageConfig());
return (await resolved).asFuture;
}
@override
Resolver get resolver {
if (_isComplete) throw BuildStepCompletedException();
final resolvers = _resolvers;
if (resolvers == null) {
throw UnsupportedError('Resolvers are not available in this build.');
}
return _DelayedResolver(_resolver ??= resolvers.get(this));
}
Future<ReleasableResolver>? _resolver;
@override
Future<bool> canRead(AssetId id) {
if (_isComplete) throw BuildStepCompletedException();
return _reader.canRead(id);
}
@override
Future<T> fetchResource<T>(Resource<T> resource) {
if (_isComplete) throw BuildStepCompletedException();
return _resourceManager.fetch(resource);
}
@override
Future<List<int>> readAsBytes(AssetId id) {
if (_isComplete) throw BuildStepCompletedException();
return _reader.readAsBytes(id);
}
@override
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) {
if (_isComplete) throw BuildStepCompletedException();
return _reader.readAsString(id, encoding: encoding);
}
@override
Stream<AssetId> findAssets(Glob glob) {
if (_isComplete) throw BuildStepCompletedException();
if (_reader is MultiPackageAssetReader) {
return (_reader as MultiPackageAssetReader)
.findAssets(glob, package: inputId.package);
} else {
return _reader.findAssets(glob);
}
}
@override
Future<void> writeAsBytes(AssetId id, FutureOr<List<int>> bytes) {
if (_isComplete) throw BuildStepCompletedException();
_checkOutput(id);
var done =
_futureOrWrite(bytes, (List<int> b) => _writer.writeAsBytes(id, b));
_writeResults.add(Result.capture(done));
return done;
}
@override
Future<void> writeAsString(AssetId id, FutureOr<String> content,
{Encoding encoding = utf8}) {
if (_isComplete) throw BuildStepCompletedException();
_checkOutput(id);
var done = _futureOrWrite(content,
(String c) => _writer.writeAsString(id, c, encoding: encoding));
_writeResults.add(Result.capture(done));
return done;
}
@override
Future<Digest> digest(AssetId id) {
if (_isComplete) throw BuildStepCompletedException();
return _reader.digest(id);
}
@override
T trackStage<T>(String label, T Function() action,
{bool isExternal = false}) =>
_stageTracker.trackStage(label, action, isExternal: isExternal);
Future<void> _futureOrWrite<T>(
FutureOr<T> content, Future<void> Function(T content) write) =>
(content is Future<T>) ? content.then(write) : write(content);
/// Waits for work to finish and cleans up resources.
///
/// This method should be called after a build has completed. After the
/// returned [Future] completes then all outputs have been written and the
/// [Resolver] for this build step - if any - has been released.
Future<void> complete() async {
_isComplete = true;
await Future.wait(_writeResults.map(Result.release));
try {
(await _resolver)?.release();
} catch (_) {}
}
/// Checks that [id] is an expected output, and throws an
/// [InvalidOutputException] or [UnexpectedOutputException] if it's not.
void _checkOutput(AssetId id) {
if (!allowedOutputs.contains(id)) {
throw UnexpectedOutputException(id, expected: allowedOutputs);
}
if (!_writtenAssets.add(id)) {
throw InvalidOutputException(
id,
'This build step already wrote to `$id`. Duplicate writes to the same '
'asset are forbidden.',
);
}
}
@override
void reportUnusedAssets(Iterable<AssetId> assets) {
_reportUnusedAssets?.call(assets);
}
}
class _DelayedResolver implements Resolver {
final Future<Resolver> _delegate;
_DelayedResolver(this._delegate);
@override
Future<bool> isLibrary(AssetId assetId) async =>
(await _delegate).isLibrary(assetId);
@override
Stream<LibraryElement> get libraries {
var completer = StreamCompleter<LibraryElement>();
_delegate.then((r) => completer.setSourceStream(r.libraries));
return completer.stream;
}
@override
Future<AstNode?> astNodeFor(Element element, {bool resolve = false}) async =>
(await _delegate).astNodeFor(element, resolve: resolve);
@override
Future<CompilationUnit> compilationUnitFor(AssetId assetId,
{bool allowSyntaxErrors = false}) async =>
(await _delegate)
.compilationUnitFor(assetId, allowSyntaxErrors: allowSyntaxErrors);
@override
Future<LibraryElement> libraryFor(AssetId assetId,
{bool allowSyntaxErrors = false}) async =>
(await _delegate)
.libraryFor(assetId, allowSyntaxErrors: allowSyntaxErrors);
@override
Future<LibraryElement?> findLibraryByName(String libraryName) async =>
(await _delegate).findLibraryByName(libraryName);
@override
Future<AssetId> assetIdForElement(Element element) async =>
(await _delegate).assetIdForElement(element);
}