blob: 33ed502f2497eeb71f3a33a13f0bdb4481256ccf [file] [log] [blame]
// Copyright (c) 2019, 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.
// @dart = 2.9
import 'dart:io';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import '../loaders/strategy.dart';
/// The URI for a particular Dart file, able to canonicalize from various
/// different representations.
class DartUri {
/// The canonical web server path part of the URI.
///
/// This is a relative path, which can be used to fetch the corresponding file
/// from the server. For example, 'hello_world/main.dart' or
/// 'packages/path/src/utils.dart'.
final String serverPath;
/// The directory in which we're running.
///
/// We store this here because for tests we may want to act as if we're
/// running in the directory of a target package, even if the current
/// directory of the tests is actually the main dwds directory.
static String currentDirectory = p.current;
/// The current directory as a file: Uri, saved here to avoid re-computing.
static String currentDirectoryUri = '${p.toUri(currentDirectory)}';
/// Load the .packages file associated with the running application so we can
/// resolve file URLs into package: URLs appropriately.
static Future<void> _loadPackageConfig(Uri uri) async {
_packageConfig ??= await loadPackageConfigUri(uri);
}
// Note that we join using the platform separator, since currentDirectory is a
// platform path, not a URI path. Then the toUri should convert them.
static Uri get _packagesUri => p.toUri(p.join(currentDirectory, '.packages'));
/// Whether the `.packages` file exists.
static bool _packagesExist;
static bool get _shouldRecord =>
_packagesExist ??= File.fromUri(_packagesUri).existsSync();
/// The way we resolve file: URLs into package: URLs
static PackageConfig _packageConfig;
/// All of the known libraries, indexed by their absolute file URL.
static final Map<String, String> _libraryNamesByPath = {};
/// Record all of the libraries, indexed by their absolute file: URI.
static Future<void> recordAbsoluteUris(Iterable<String> libraryUris) async {
if (_shouldRecord) {
await _loadPackageConfig(_packagesUri);
_libraryNamesByPath.clear();
for (var uri in libraryUris) {
_recordAbsoluteUri(uri);
}
}
}
/// Record the library represented by package: or org-dartlang-app: uris
/// indexed by absolute file: URI.
static void _recordAbsoluteUri(String libraryUri) {
var uri = Uri.parse(libraryUri);
if (uri.scheme == 'dart' ||
(uri.scheme == '' && !uri.path.endsWith('.dart'))) {
// We ignore dart: libraries, and non-Dart libraries referenced by path.
// e.g. main.dart.bootstrap
// TODO(alanknight): These should not be showing up in the library list,
// fix _getLibraryRefs and then remove this check.
} else if (uri.scheme == 'org-dartlang-app' || uri.scheme == 'google3') {
// Both currentDirectoryUri and the libraryUri path should have '/'
// separators, so we can join them as url paths to get the absolute file
// url.
var libraryPath =
p.url.join('$currentDirectoryUri', uri.path.substring(1));
_libraryNamesByPath[libraryPath] = libraryUri;
} else if (uri.scheme == 'package') {
var libraryPath = _packageConfig.resolve(uri);
_libraryNamesByPath['$libraryPath'] = libraryUri;
} else {
throw ArgumentError.value(libraryUri, 'URI scheme not allowed');
}
}
/// Accepts various forms of URI and can convert between forms.
///
/// The accepted forms are:
///
/// - package:packageName/pathUnderLib/file.dart
/// - org-dartlang-app:///prefix/path/file.dart, where prefix is ignored.
/// e.g. org-dartlang-app:example/hello_world/main.dart,
/// - /packages/packageName/foo.dart, the web server form of a package URI,
/// e.g. /packages/path/src/utils.dart
/// - /path/foo.dart or path/foo.dart, e.g. /hello_world/web/main.dart, where
/// path is a web server path and so relative to the directory being
/// served, not to the package.
///
/// The optional [serverUri] is a temporary workaround for a bug with construction.
/// Older SDKs (before D24) gave us a path that didn't include the full path,
/// e.g. main.dart rather than hello_world/main.dart and src/path.dart rather than
/// packages/path/src/path.dart. The optional [serverUri] is the full URI of the
/// JS script. The dirname of that path should give us the missing prefix.
factory DartUri(String uri, [String serverUri]) {
var serverPath = globalLoadStrategy.serverPathForAppUri(uri);
if (serverPath != null) return DartUri._(serverPath);
if (uri.startsWith('package:')) {
return DartUri._fromPackageUri(uri, serverUri: serverUri);
}
if (uri.startsWith('file:')) return DartUri._fromFileUri(uri);
if (uri.startsWith('/packages/')) {
return DartUri._fromRelativePath(uri, serverUri: serverUri);
}
if (uri.startsWith('/')) return DartUri._fromRelativePath(uri);
if (uri.startsWith('http:') || uri.startsWith('https:')) {
return DartUri(Uri.parse(uri).path);
}
throw FormatException('Unsupported URI form', uri);
}
/// Returns the dirname for the server URI.
static String _dirForServerUri(String uri) => p.dirname(Uri.parse(uri).path);
/// Construct from a package: URI
factory DartUri._fromPackageUri(String uri, {String serverUri}) {
var packagePath = 'packages/${uri.substring("package:".length)}';
if (serverUri != null) {
return DartUri._fromRelativePath(
p.url.join(_dirForServerUri(serverUri), packagePath));
}
return DartUri._(packagePath);
}
/// Construct from a file: URI
factory DartUri._fromFileUri(String uri) {
var libraryName = _libraryNamesByPath[uri];
if (libraryName != null) return DartUri(libraryName);
// This is not one of our recorded libraries.
throw ArgumentError.value(uri, 'uri', 'Unknown library');
}
DartUri._(this.serverPath);
/// Construct from a path, relative to the directory being served.
factory DartUri._fromRelativePath(String uri, {String serverUri}) {
uri = uri[0] == '.' ? uri.substring(1) : uri;
uri = uri[0] == '/' ? uri.substring(1) : uri;
if (serverUri != null) {
return DartUri._fromRelativePath(
p.url.join(_dirForServerUri(serverUri), uri));
}
return DartUri._(uri);
}
}