| // 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; |
| } |
| } |
| } |