blob: 15b8e1885f051464e5685055eef381044bc06196 [file] [log] [blame]
// Copyright (c) 2014, 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:package_config/packages_file.dart' as packages_file;
import 'package:path/path.dart' as p;
/// [Resolver] resolves imports with respect to a given environment.
class Resolver {
final String packagesPath;
final String packageRoot;
final String sdkRoot;
final List<String> failed = [];
Map<String, Uri> _packages;
Resolver({String packagesPath, this.packageRoot, this.sdkRoot})
: packagesPath = packagesPath,
_packages = packagesPath != null ? _parsePackages(packagesPath) : null;
/// Returns the absolute path wrt. to the given environment or null, if the
/// import could not be resolved.
String resolve(String scriptUri) {
var uri = Uri.parse(scriptUri);
if (uri.scheme == 'dart') {
if (sdkRoot == null) {
// No sdk-root given, do not resolve dart: URIs.
return null;
}
String filePath;
if (uri.pathSegments.length > 1) {
var path = uri.pathSegments[0];
// Drop patch files, since we don't have their source in the compiled
// SDK.
if (path.endsWith('-patch')) {
failed.add('$uri');
return null;
}
// Canonicalize path. For instance: _collection-dev => _collection_dev.
path = path.replaceAll('-', '_');
var pathSegments = [sdkRoot, path]..addAll(uri.pathSegments.sublist(1));
filePath = p.joinAll(pathSegments);
} else {
// Resolve 'dart:something' to be something/something.dart in the SDK.
var lib = uri.path;
filePath = p.join(sdkRoot, lib, '$lib.dart');
}
return resolveSymbolicLinks(filePath);
}
if (uri.scheme == 'package') {
if (packagesPath == null && packageRoot == null) {
// No package-root given, do not resolve package: URIs.
return null;
}
var packageName = uri.pathSegments[0];
if (_packages != null) {
var packageUri = _packages[packageName];
if (packageUri == null) {
failed.add('$uri');
return null;
}
var packagePath = p.fromUri(packageUri);
var pathInPackage = p.joinAll(uri.pathSegments.sublist(1));
return resolveSymbolicLinks(p.join(packagePath, pathInPackage));
}
return resolveSymbolicLinks(p.join(packageRoot, uri.path));
}
if (uri.scheme == 'file') {
return resolveSymbolicLinks(p.fromUri(uri));
}
// We cannot deal with anything else.
failed.add('$uri');
return null;
}
/// Returns a canonicalized path, or `null` if the path cannot be resolved.
String resolveSymbolicLinks(String path) {
var normalizedPath = p.normalize(path);
var type = FileSystemEntity.typeSync(normalizedPath, followLinks: true);
if (type == FileSystemEntityType.notFound) return null;
return new File(normalizedPath).resolveSymbolicLinksSync();
}
static Map<String, Uri> _parsePackages(String packagesPath) {
var source = new File(packagesPath).readAsBytesSync();
return packages_file.parse(source, new Uri.file(packagesPath));
}
}
/// Bazel URI resolver.
class BazelResolver extends Resolver {
final String workspacePath;
/// Creates a Bazel resolver with the specified workspace path, if any.
BazelResolver({this.workspacePath: ''});
/// Returns the absolute path wrt. to the given environment or null, if the
/// import could not be resolved.
@override
String resolve(String scriptUri) {
var uri = Uri.parse(scriptUri);
if (uri.scheme == 'dart') {
// Ignore the SDK
return null;
}
if (uri.scheme == 'package') {
// TODO(cbracken) belongs in a Bazel package
return _resolveBazelPackage(uri.pathSegments);
}
if (uri.scheme == 'file') {
var runfilesPathSegment = '.runfiles/$workspacePath';
runfilesPathSegment =
runfilesPathSegment.replaceAll(new RegExp(r'/*$'), '/');
var runfilesPos = uri.path.indexOf(runfilesPathSegment);
if (runfilesPos >= 0) {
int pathStart = runfilesPos + runfilesPathSegment.length;
return uri.path.substring(pathStart);
}
return null;
}
if (uri.scheme == 'https' || uri.scheme == 'http') {
return _extractHttpPath(uri);
}
// We cannot deal with anything else.
failed.add('$uri');
return null;
}
String _extractHttpPath(Uri uri) {
int packagesPos = uri.pathSegments.indexOf('packages');
if (packagesPos >= 0) {
var workspacePath = uri.pathSegments.sublist(packagesPos + 1);
return _resolveBazelPackage(workspacePath);
}
return uri.pathSegments.join('/');
}
String _resolveBazelPackage(List<String> pathSegments) {
// TODO(cbracken) belongs in a Bazel package
var packageName = pathSegments[0];
var pathInPackage = pathSegments.sublist(1).join('/');
String packagePath;
if (packageName.contains('.')) {
packagePath = packageName.replaceAll('.', '/');
} else {
packagePath = 'third_party/dart/$packageName';
}
return '$packagePath/lib/$pathInPackage';
}
}
/// Loads the lines of imported resources.
class Loader {
final List<String> failed = [];
/// Loads an imported resource and returns a [Future] with a [List] of lines.
/// Returns `null` if the resource could not be loaded.
Future<List<String>> load(String path) async {
try {
return new File(path).readAsLines();
} catch (_) {
failed.add(path);
return null;
}
}
}