blob: cb5b7288943812b5f17ca8633a748682dc291007 [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:io';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
/// [Resolver] resolves imports with respect to a given environment.
class Resolver {
@Deprecated('Use Resolver.create')
Resolver({this.packagesPath, this.sdkRoot})
: _packages = packagesPath != null ? _parsePackages(packagesPath) : null,
packagePath = null;
Resolver._(
{this.packagesPath,
this.packagePath,
this.sdkRoot,
Map<String, Uri>? packages})
: _packages = packages;
static Future<Resolver> create({
String? packagesPath,
String? packagePath,
String? sdkRoot,
}) async {
return Resolver._(
packagesPath: packagesPath,
packagePath: packagePath,
sdkRoot: sdkRoot,
packages: packagesPath != null
? _parsePackages(packagesPath)
: (packagePath != null ? await _parsePackage(packagePath) : null),
);
}
final String? packagesPath;
final String? packagePath;
final String? sdkRoot;
final List<String> failed = [];
final Map<String, Uri>? _packages;
/// Returns the absolute path wrt. to the given environment or null, if the
/// import could not be resolved.
String? resolve(String scriptUri) {
final uri = Uri.parse(scriptUri);
if (uri.scheme == 'dart') {
final sdkRoot = this.sdkRoot;
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('-', '_');
final pathSegments = [
sdkRoot,
path,
...uri.pathSegments.sublist(1),
];
filePath = p.joinAll(pathSegments);
} else {
// Resolve 'dart:something' to be something/something.dart in the SDK.
final lib = uri.path;
filePath = p.join(sdkRoot, lib, '$lib.dart');
}
return resolveSymbolicLinks(filePath);
}
if (uri.scheme == 'package') {
final packages = _packages;
if (packages == null) {
return null;
}
final packageName = uri.pathSegments[0];
final packageUri = packages[packageName];
if (packageUri == null) {
failed.add('$uri');
return null;
}
final packagePath = p.fromUri(packageUri);
final pathInPackage = p.joinAll(uri.pathSegments.sublist(1));
return resolveSymbolicLinks(p.join(packagePath, pathInPackage));
}
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) {
final normalizedPath = p.normalize(path);
final type = FileSystemEntity.typeSync(normalizedPath, followLinks: true);
if (type == FileSystemEntityType.notFound) return null;
return File(normalizedPath).resolveSymbolicLinksSync();
}
static Map<String, Uri> _parsePackages(String packagesPath) {
final content = File(packagesPath).readAsStringSync();
final packagesUri = p.toUri(packagesPath);
final parsed =
PackageConfig.parseString(content, Uri.base.resolveUri(packagesUri));
return {
for (var package in parsed.packages) package.name: package.packageUriRoot
};
}
static Future<Map<String, Uri>?> _parsePackage(String packagePath) async {
final parsed = await findPackageConfig(Directory(packagePath));
if (parsed == null) return null;
return {
for (var package in parsed.packages) package.name: package.packageUriRoot
};
}
}
/// Bazel URI resolver.
class BazelResolver extends Resolver {
/// Creates a Bazel resolver with the specified workspace path, if any.
BazelResolver({this.workspacePath = ''});
final String workspacePath;
/// Returns the absolute path wrt. to the given environment or null, if the
/// import could not be resolved.
@override
String? resolve(String scriptUri) {
final 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') {
final runfilesPathSegment =
'.runfiles/$workspacePath'.replaceAll(RegExp(r'/*$'), '/');
final runfilesPos = uri.path.indexOf(runfilesPathSegment);
if (runfilesPos >= 0) {
final 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) {
final packagesPos = uri.pathSegments.indexOf('packages');
if (packagesPos >= 0) {
final 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
final packageName = pathSegments[0];
final pathInPackage = pathSegments.sublist(1).join('/');
final packagePath = packageName.contains('.')
? packageName.replaceAll('.', '/')
: '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 {
// Ensure `readAsLines` runs within the try block so errors are caught.
return await File(path).readAsLines();
} catch (_) {
failed.add(path);
return null;
}
}
/// Loads an imported resource and returns a [List] of lines.
/// Returns `null` if the resource could not be loaded.
List<String>? loadSync(String path) {
try {
// Ensure `readAsLinesSync` runs within the try block so errors are
// caught.
return File(path).readAsLinesSync();
} catch (_) {
failed.add(path);
return null;
}
}
}