blob: 7dcdd34a79ef7f2d4df0b862485bde78c0d6efd3 [file] [log] [blame]
// Copyright (c) 2013, 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:isolate';
import 'package:barback/barback.dart';
import 'package:source_span/source_span.dart';
import 'package:stack_trace/stack_trace.dart';
import '../asset/dart/serialize.dart';
import '../barback.dart';
import '../dart.dart' as dart;
import '../exceptions.dart';
import '../log.dart' as log;
import '../utils.dart';
import 'asset_environment.dart';
import 'barback_server.dart';
import 'foreign_transformer.dart';
import 'transformer_config.dart';
import 'transformer_id.dart';
/// A wrapper for an isolate from which transformer plugins can be instantiated.
class TransformerIsolate {
/// The port used to communicate with the wrapped isolate.
final SendPort _port;
/// A map indicating the barback server URLs for each [TransformerId] that's
/// loaded in the wrapped isolate.
///
/// A barback server URL is the URL for the library that the given id
/// identifies. For example, the URL for "polymer/src/mirrors_remover" might
/// be "http://localhost:56234/packages/polymer/src/mirrors_remover.dart".
final Map<TransformerId, Uri> _idsToUrls;
/// The barback mode for this run of pub.
final BarbackMode _mode;
/// Spawns an isolate that loads all transformer libraries defined by [ids].
///
/// This doesn't actually instantiate any transformers, since a
/// [TransformerId] doesn't define the transformers' configuration. The
/// transformers can be constructed using [create].
///
/// If [snapshot] is passed, the isolate will be loaded from that path if it
/// exists. Otherwise, a snapshot of the isolate's code will be saved to that
/// path once the isolate is loaded.
static Future<TransformerIsolate> spawn(AssetEnvironment environment,
BarbackServer transformerServer, List<TransformerId> ids,
{String snapshot}) async {
var idsToAssetIds = <TransformerId, AssetId>{};
var idsToUrls = <TransformerId, Uri>{};
await Future.wait(ids.map((id) async {
var assetId = await id.getAssetId(environment.barback);
idsToAssetIds[id] = assetId;
var path = assetId.path.replaceFirst('lib/', '');
idsToUrls[id] = Uri.parse('package:${id.package}/$path');
}));
var code = new StringBuffer();
code.writeln("import 'dart:isolate';");
for (var url in idsToUrls.values) {
code.writeln("import '$url';");
}
code.writeln("import r'package:\$pub/transformer_isolate.dart';");
code.writeln(
"void main(_, SendPort replyTo) => loadTransformers(replyTo);");
log.fine("Loading transformers from $ids");
var port = new ReceivePort();
try {
await dart.runInIsolate(code.toString(), port.sendPort,
packageRoot: transformerServer.url.resolve('packages'),
snapshot: snapshot);
return new TransformerIsolate._(
await port.first, environment.mode, idsToUrls);
} on IsolateSpawnException catch (error, stackTrace) {
// TODO(nweiz): don't parse this as a string once issues 12617 and 12689
// are fixed.
var firstErrorLine = error.message.split('\n')[1];
// The isolate error message contains the fully expanded path, not the
// "package:" URI, so we have to be liberal in what we look for in the
// error message.
var missingTransformer = idsToUrls.keys.firstWhere(
(id) =>
firstErrorLine.startsWith('Could not import "${idsToUrls[id]}"'),
orElse: () => throw error);
var packageUri = idToPackageUri(idsToAssetIds[missingTransformer]);
// If there was an IsolateSpawnException and the import that actually
// failed was the one we were loading transformers from, throw an
// application exception with a more user-friendly message.
fail('Transformer library "$packageUri" not found.', error, stackTrace);
}
}
TransformerIsolate._(this._port, this._mode, this._idsToUrls);
/// Instantiate the transformers in the [config.id] with
/// [config.configuration].
///
/// If there are no transformers defined in the given library, this will
/// return an empty set.
Future<Set<Transformer>> create(TransformerConfig config) async {
try {
var transformers = (await call<List>(_port, {
'library': _idsToUrls[config.id].toString(),
'mode': _mode.name,
'configuration': JSON.encode(config.configuration)
}))
.map((transformer) => deserializeTransformerLike(transformer, config))
.toSet();
log.fine("Transformers from $config: $transformers");
return transformers;
} catch (error) {
throw new TransformerLoadError(error, config.span);
}
}
}
/// An error thrown when a transformer fails to load.
class TransformerLoadError extends SourceSpanException
implements WrappedException {
final CrossIsolateException innerError;
Chain get innerChain => innerError.stackTrace;
TransformerLoadError(CrossIsolateException error, SourceSpan span)
: innerError = error,
super("Error loading transformer: ${error.message}", span);
}