blob: 474a742386eaf94acd3757b25018227a41ec7e8a [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:io';
import 'package:barback/barback.dart';
import 'package:path/path.dart' as p;
import '../io.dart';
typedef Future<Asset> AssetReader(AssetId id);
/// An on-disk temporary environment for running executables that don't have
/// a standard Dart library API.
class ScratchSpace {
final Directory tempDir;
final Directory packagesDir;
final AssetReader getAsset;
// Assets which have a file created but it is still being written to.
final _pendingWrites = <AssetId, Future>{};
ScratchSpace._(Directory tempDir, this.getAsset)
: packagesDir = new Directory(p.join(tempDir.path, 'packages')),
this.tempDir = tempDir;
factory ScratchSpace(Future<Asset> getAsset(AssetId id)) {
var tempDir = new Directory(createSystemTempDir());
return new ScratchSpace._(tempDir, getAsset);
}
/// Copies [assetIds] to [tempDir] if they don't exist.
///
/// Any [Asset] that is under a `lib` dir will be output under a `packages`
/// directory corresponding to its package, and any other assets are output
/// directly under the temp dir using their unmodified path.
Future ensureAssets(Iterable<AssetId> assetIds) async {
var futures = <Future>[];
for (var id in assetIds) {
var file = fileFor(id);
if (file.existsSync()) {
var pending = _pendingWrites[id];
if (pending != null) futures.add(pending);
} else {
file.createSync(recursive: true);
var done = () async {
var asset = await getAsset(id);
await createFileFromStream(asset.read(), file.path);
_pendingWrites.remove(id);
}();
_pendingWrites[id] = done;
futures.add(done);
}
}
return Future.wait(futures);
}
/// Deletes all files for [package] from the temp dir (synchronously).
///
/// This always deletes the [package] dir under [packagesDir].
///
/// If [isRootPackage] then this also deletes all top level entities under
/// [tempDir] other than the [packagesDir].
void deletePackageFiles(String package, {bool isRootPackage}) {
isRootPackage ??= false;
var packageDir = new Directory(p.join(packagesDir.path, package));
if (packageDir.existsSync()) packageDir.deleteSync(recursive: true);
if (isRootPackage) {
var entities = tempDir.listSync(recursive: false);
for (var entity in entities) {
if (entity.path == packagesDir.path) continue;
entity.deleteSync(recursive: true);
}
}
}
/// Deletes the temp directory for this environment.
Future delete() async {
if (await tempDir.exists()) return tempDir.delete(recursive: true);
}
/// Returns the actual [File] in this environment corresponding to [id].
///
/// The returned [File] may or may not already exist.
File fileFor(AssetId id) =>
new File(p.join(tempDir.path, _relativePathFor(id)));
}
/// Returns a canonical uri for [id].
///
/// If [id] is under a `lib` directory then this returns a `package:` uri,
/// otherwise it just returns [id.path].
String canonicalUriFor(AssetId id) {
if (topLevelDir(id.path) == 'lib') {
var packagePath =
p.url.join(id.package, p.url.joinAll(p.url.split(id.path).skip(1)));
return 'package:$packagePath';
} else {
return id.path;
}
}
/// The path relative to the root of the environment for a given [id].
String _relativePathFor(AssetId id) =>
canonicalUriFor(id).replaceFirst('package:', 'packages/');