blob: f3c0131c32fd683439b7239284320842a8cc1aee [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:async/async.dart';
import 'package:build/build.dart';
import 'package:crypto/crypto.dart';
import 'package:glob/glob.dart';
import 'package:meta/meta.dart';
import '../asset_graph/graph.dart';
import '../asset_graph/node.dart';
import '../util/async.dart';
/// A [RunnerAssetReader] must implement [MultiPackageAssetReader].
abstract class RunnerAssetReader implements MultiPackageAssetReader {}
/// An [AssetReader] that can provide actual paths to assets on disk.
abstract class PathProvidingAssetReader implements AssetReader {
String pathTo(AssetId id);
}
/// Describes if and how a [SingleStepReader] should read an [AssetId].
class Readability {
final bool canRead;
final bool inSamePhase;
const Readability({@required this.canRead, @required this.inSamePhase});
/// Determines readability for a node written in a previous build phase, which
/// means that [ownOutput] is impossible.
factory Readability.fromPreviousPhase(bool readable) =>
readable ? Readability.readable : Readability.notReadable;
static const Readability notReadable =
Readability(canRead: false, inSamePhase: false);
static const Readability readable =
Readability(canRead: true, inSamePhase: false);
static const Readability ownOutput =
Readability(canRead: true, inSamePhase: true);
}
typedef IsReadable = FutureOr<Readability> Function(
AssetNode node, int phaseNum, AssetWriterSpy writtenAssets);
/// An [AssetReader] with a lifetime equivalent to that of a single step in a
/// build.
///
/// A step is a single Builder and primary input (or package for package
/// builders) combination.
///
/// Limits reads to the assets which are sources or were generated by previous
/// phases.
///
/// Tracks the assets and globs read during this step for input dependency
/// tracking.
class SingleStepReader implements AssetReader {
final AssetGraph _assetGraph;
final AssetReader _delegate;
final int _phaseNumber;
final String _primaryPackage;
final AssetWriterSpy _writtenAssets;
final IsReadable _isReadableNode;
final FutureOr<GlobAssetNode> Function(
Glob glob, String package, int phaseNum) _getGlobNode;
/// The assets read during this step.
final assetsRead = HashSet<AssetId>();
SingleStepReader(this._delegate, this._assetGraph, this._phaseNumber,
this._primaryPackage, this._isReadableNode,
[this._getGlobNode, this._writtenAssets]);
/// Checks whether [id] can be read by this step - attempting to build the
/// asset if necessary.
FutureOr<bool> _isReadable(AssetId id) {
final node = _assetGraph.get(id);
if (node == null) {
assetsRead.add(id);
_assetGraph.add(SyntheticSourceAssetNode(id));
return false;
}
return doAfter(_isReadableNode(node, _phaseNumber, _writtenAssets),
(Readability readability) {
if (!readability.inSamePhase) {
assetsRead.add(id);
}
return readability.canRead;
});
}
@override
Future<bool> canRead(AssetId id) {
return toFuture(doAfter(_isReadable(id), (bool isReadable) {
if (!isReadable) return false;
var node = _assetGraph.get(id);
FutureOr<bool> _canRead() {
if (node is GeneratedAssetNode) {
// Short circut, we know this file exists because its readable and it was
// output.
return true;
} else {
return _delegate.canRead(id);
}
}
return doAfter(_canRead(), (bool canRead) {
if (!canRead) return false;
return doAfter(_ensureDigest(id), (_) => true);
});
}));
}
@override
Future<Digest> digest(AssetId id) {
return toFuture(doAfter(_isReadable(id), (bool isReadable) {
if (!isReadable) {
return Future.error(AssetNotFoundException(id));
}
return _ensureDigest(id);
}));
}
@override
Future<List<int>> readAsBytes(AssetId id) {
return toFuture(doAfter(_isReadable(id), (bool isReadable) {
if (!isReadable) {
return Future.error(AssetNotFoundException(id));
}
return doAfter(_ensureDigest(id), (_) => _delegate.readAsBytes(id));
}));
}
@override
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) {
return toFuture(doAfter(_isReadable(id), (bool isReadable) {
if (!isReadable) {
return Future.error(AssetNotFoundException(id));
}
return doAfter(_ensureDigest(id),
(_) => _delegate.readAsString(id, encoding: encoding));
}));
}
@override
Stream<AssetId> findAssets(Glob glob) {
var streamCompleter = StreamCompleter<AssetId>();
doAfter(_getGlobNode(glob, _primaryPackage, _phaseNumber),
(GlobAssetNode globNode) {
assetsRead.add(globNode.id);
streamCompleter.setSourceStream(Stream.fromIterable(globNode.results));
});
return streamCompleter.stream;
}
FutureOr<Digest> _ensureDigest(AssetId id) {
var node = _assetGraph.get(id);
if (node?.lastKnownDigest != null) return node.lastKnownDigest;
return _delegate.digest(id).then((digest) => node.lastKnownDigest = digest);
}
}