blob: f38d3fc39caf9c8a817f0f70da7e402f8fd5de46 [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:convert';
import 'dart:io';
import 'package:build/build.dart';
import 'package:glob/glob.dart';
import 'package:package_resolver/package_resolver.dart';
import 'package:path/path.dart' as p;
import 'package:stream_transform/stream_transform.dart';
/// Resolves using a [SyncPackageResolver] before reading from the file system.
///
/// For a simple implementation that uses the current isolate's package
/// resolution logic (i.e. whatever you have generated in `.packages` in most
/// cases), use [currentIsolate]:
/// ```dart
/// var assetReader = await PackageAssetReader.currentIsolate();
/// ```
class PackageAssetReader extends AssetReader
implements MultiPackageAssetReader {
final SyncPackageResolver _packageResolver;
/// What package is the originating build occurring in.
final String _rootPackage;
/// Wrap a [SyncPackageResolver] to identify where files are located.
///
/// To use a normal [PackageResolver] use `asSync`:
/// ```
/// new PackageAssetReader(await packageResolver.asSync);
/// ```
PackageAssetReader(this._packageResolver, [this._rootPackage]);
/// A [PackageAssetReader] with a single [packageRoot] configured.
///
/// It is assumed that every _directory_ in [packageRoot] is a package where
/// the name of the package is the name of the directory. This is similar to
/// the older "packages" folder paradigm for resolution.
factory PackageAssetReader.forPackageRoot(String packageRoot,
[String rootPackage]) {
// This purposefully doesn't use SyncPackageResolver.root, because that is
// assuming a symlink collection and not directories, and this factory is
// more useful for a user-created collection of folders for testing.
final directory = Directory(packageRoot);
final packages = <String, String>{};
for (final entity in directory.listSync()) {
if (entity is Directory) {
final name = p.basename(entity.path);
packages[name] = entity.uri.toString();
}
}
return PackageAssetReader.forPackages(packages, rootPackage);
}
/// Returns a [PackageAssetReader] with a simple [packageToPath] mapping.
factory PackageAssetReader.forPackages(Map<String, String> packageToPath,
[String rootPackage]) =>
PackageAssetReader(
SyncPackageResolver.config(packageToPath.map((k, v) => MapEntry(
k, Uri.parse(p.absolute(v, 'lib')).replace(scheme: 'file')))),
rootPackage);
/// A reader that can resolve files known to the current isolate.
///
/// A [rootPackage] should be provided for full API compatibility.
static Future<PackageAssetReader> currentIsolate({String rootPackage}) async {
var resolver = PackageResolver.current;
return PackageAssetReader(await resolver.asSync, rootPackage);
}
File _resolve(AssetId id) {
final uri = id.uri;
if (uri.isScheme('package')) {
final uri = _packageResolver.resolveUri(id.uri);
if (uri != null) {
return File.fromUri(uri);
}
}
if (id.package == _rootPackage) {
return File(p.canonicalize(p.join(_rootPackagePath, id.path)));
}
return null;
}
String get _rootPackagePath {
// If the root package has a pub layout, use `packagePath`.
final root = _packageResolver.packagePath(_rootPackage);
if (Directory(p.join(root, 'lib')).existsSync()) {
return root;
}
// Assume the cwd is the package root.
return p.current;
}
@override
Stream<AssetId> findAssets(Glob glob, {String package}) {
package ??= _rootPackage;
if (package == null) {
throw UnsupportedError(
'Root package must be provided to use `findAssets` without an '
'explicit `package`.');
}
var packageLibDir = _packageResolver.packageConfigMap[package];
if (packageLibDir == null) {
throw UnsupportedError('Unable to find package $package');
}
var packageFiles = Directory.fromUri(packageLibDir)
.list(recursive: true)
.where((e) => e is File)
.map((f) =>
p.join('lib', p.relative(f.path, from: p.fromUri(packageLibDir))));
if (package == _rootPackage) {
packageFiles = packageFiles.transform(merge(Directory(_rootPackagePath)
.list(recursive: true)
.where((e) => e is File)
.map((f) => p.relative(f.path, from: _rootPackagePath))
.where((p) => !(p.startsWith('packages/') || p.startsWith('lib/')))));
}
return packageFiles.where(glob.matches).map((p) => AssetId(package, p));
}
@override
Future<bool> canRead(AssetId id) =>
_resolve(id)?.exists() ?? Future.value(false);
@override
Future<List<int>> readAsBytes(AssetId id) =>
_resolve(id)?.readAsBytes() ?? (throw AssetNotFoundException(id));
@override
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
_resolve(id)?.readAsString(encoding: encoding) ??
(throw AssetNotFoundException(id));
}