blob: a8f9db7fabe8cf6d0090a725e0d1c7389970017e [file] [log] [blame]
// Copyright (c) 2016, 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:collection';
import 'dart:io' as io;
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_engine_io.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
Version languageVersionFromSdkVersion(String sdkVersionStr) {
var sdkVersionParts = sdkVersionStr.split('.');
var sdkVersionMajor = int.parse(sdkVersionParts[0]);
var sdkVersionMinor = int.parse(sdkVersionParts[1]);
return Version(sdkVersionMajor, sdkVersionMinor, 0);
}
/// An abstract implementation of a Dart SDK in which the available libraries
/// are stored in a library map. Subclasses are responsible for populating the
/// library map.
abstract class AbstractDartSdk implements DartSdk {
/// The resource provider used to access the file system.
late final ResourceProvider resourceProvider;
/// A mapping from Dart library URI's to the library represented by that URI.
LibraryMap libraryMap = LibraryMap();
/// The mapping from Dart URI's to the corresponding sources.
final Map<String, Source?> _uriToSourceMap = HashMap<String, Source?>();
@override
List<SdkLibraryImpl> get sdkLibraries => libraryMap.sdkLibraries;
/// Return the path separator used by the resource provider.
String get separator => resourceProvider.pathContext.separator;
@override
List<String> get uris => libraryMap.uris;
/// Return info for debugging https://github.com/dart-lang/sdk/issues/35226.
Map<String, Object> debugInfo() {
return <String, Object>{
'runtimeType': '$runtimeType',
'libraryMap': libraryMap.debugInfo(),
};
}
@override
Source? fromFileUri(Uri uri) {
File file =
resourceProvider.getFile(resourceProvider.pathContext.fromUri(uri));
String? path = _getPath(file);
if (path == null) {
return null;
}
try {
return file.createSource(Uri.parse(path));
} on FormatException catch (exception, stackTrace) {
AnalysisEngine.instance.instrumentationService.logInfo(
"Failed to create URI: $path",
CaughtException(exception, stackTrace));
}
return null;
}
String? getRelativePathFromFile(File file);
@override
SdkLibrary? getSdkLibrary(String dartUri) => libraryMap.getLibrary(dartUri);
Source? internalMapDartUri(String dartUri) {
// TODO(brianwilkerson) Figure out how to unify the implementations in the
// two subclasses.
String libraryName;
String relativePath;
int index = dartUri.indexOf('/');
if (index >= 0) {
libraryName = dartUri.substring(0, index);
relativePath = dartUri.substring(index + 1);
} else {
libraryName = dartUri;
relativePath = "";
}
SdkLibrary? library = getSdkLibrary(libraryName);
if (library == null) {
return null;
}
String srcPath;
if (relativePath.isEmpty) {
srcPath = library.path;
} else {
String libraryPath = library.path;
int index = libraryPath.lastIndexOf(separator);
if (index == -1) {
index = libraryPath.lastIndexOf('/');
if (index == -1) {
return null;
}
}
String prefix = libraryPath.substring(0, index + 1);
srcPath = '$prefix$relativePath';
}
String filePath = srcPath.replaceAll('/', separator);
try {
File file = resourceProvider.getFile(filePath);
return file.createSource(Uri.parse(dartUri));
} on FormatException {
return null;
}
}
@override
Source? mapDartUri(String dartUri) {
Source? source = _uriToSourceMap[dartUri];
if (source == null) {
source = internalMapDartUri(dartUri);
_uriToSourceMap[dartUri] = source;
}
return source;
}
@override
Uri? pathToUri(String path) {
var file = resourceProvider.getFile(path);
var uriStr = _getPath(file);
if (uriStr == null) {
return null;
}
try {
return Uri.parse(uriStr);
} on FormatException {
return null;
}
}
/// TODO(scheglov) This name is misleading, returns `dart:foo/bar.dart`.
String? _getPath(File file) {
// TODO(scheglov) Ideally, all `SdkLibrary` should have `File`, with
// an absolute path, not a relative `path`. For now, we do this only
// for building SDK summary with `dart:ui`, included from outside
// of the SDK root.
for (final library in libraryMap.sdkLibraries) {
final pathContext = resourceProvider.pathContext;
if (pathContext.isAbsolute(library.path)) {
if (library.path == file.path) {
return library.shortName;
}
}
}
for (final library in libraryMap.sdkLibraries) {
final pathContext = resourceProvider.pathContext;
if (pathContext.isAbsolute(library.path)) {
final libraryFile = resourceProvider.getFile(library.path);
final libraryFolder = libraryFile.parent;
if (libraryFolder.contains(file.path)) {
final relPath = pathContext
.relative(file.path, from: libraryFolder.path)
.replaceAll(separator, '/');
return '${library.shortName}/$relPath';
}
}
}
List<SdkLibrary> libraries = libraryMap.sdkLibraries;
int length = libraries.length;
String? filePath = getRelativePathFromFile(file);
if (filePath == null) {
return null;
}
List<String> paths = <String>[];
for (int i = 0; i < length; i++) {
SdkLibrary library = libraries[i];
String libraryPath = library.path.replaceAll('/', separator);
if (filePath == libraryPath) {
return library.shortName;
}
paths.add(libraryPath);
}
for (int i = 0; i < length; i++) {
SdkLibrary library = libraries[i];
String libraryPath = paths[i];
int index = libraryPath.lastIndexOf(separator);
if (index >= 0) {
String prefix = libraryPath.substring(0, index + 1);
if (filePath.startsWith(prefix)) {
String relPath =
filePath.substring(prefix.length).replaceAll(separator, '/');
return '${library.shortName}/$relPath';
}
}
}
return null;
}
}
/// An SDK backed by URI mappings derived from an `_embedder.yaml` file.
class EmbedderSdk extends AbstractDartSdk {
static const String _DART_COLON_PREFIX = 'dart:';
static const String _EMBEDDED_LIB_MAP_KEY = 'embedded_libs';
late final Version _languageVersion;
final Map<String, String> _urlMappings = HashMap<String, String>();
/// TODO(scheglov) Make [languageVersion] required.
/// https://github.com/dart-lang/sdk/issues/42890
EmbedderSdk(
ResourceProvider resourceProvider,
Map<Folder, YamlMap>? embedderYamls, {
Version? languageVersion,
}) {
this.resourceProvider = resourceProvider;
_languageVersion =
languageVersion ?? languageVersionFromSdkVersion(io.Platform.version);
embedderYamls?.forEach(_processEmbedderYaml);
}
@override
String? get allowedExperimentsJson {
var coreSource = mapDartUri('dart:core');
if (coreSource != null) {
var coreFile = resourceProvider.getFile(coreSource.fullName);
var embeddedFolder = coreFile.parent.parent;
try {
return embeddedFolder
.getChildAssumingFolder('_internal')
.getChildAssumingFile('allowed_experiments.json')
.readAsStringSync();
} catch (_) {}
}
return null;
}
@override
Version get languageVersion => _languageVersion;
@override
// TODO(danrubel) Determine SDK version
String get sdkVersion => '0';
/// The url mappings for this SDK.
Map<String, String> get urlMappings => _urlMappings;
@override
String getRelativePathFromFile(File file) => file.path;
@override
Source? internalMapDartUri(String dartUri) {
String libraryName;
String relativePath;
int index = dartUri.indexOf('/');
if (index >= 0) {
libraryName = dartUri.substring(0, index);
relativePath = dartUri.substring(index + 1);
} else {
libraryName = dartUri;
relativePath = "";
}
SdkLibrary? library = getSdkLibrary(libraryName);
if (library == null) {
return null;
}
String srcPath;
if (relativePath.isEmpty) {
srcPath = library.path;
} else {
String libraryPath = library.path;
int index = libraryPath.lastIndexOf(separator);
if (index == -1) {
index = libraryPath.lastIndexOf('/');
if (index == -1) {
return null;
}
}
String prefix = libraryPath.substring(0, index + 1);
srcPath = '$prefix$relativePath';
}
String filePath = srcPath.replaceAll('/', separator);
try {
File file = resourceProvider.getFile(filePath);
return file.createSource(Uri.parse(dartUri));
} on FormatException {
return null;
}
}
/// Install the mapping from [name] to [libDir]/[file].
void _processEmbeddedLibs(String name, String file, Folder libDir) {
if (!name.startsWith(_DART_COLON_PREFIX)) {
// SDK libraries must begin with 'dart:'.
return;
}
String libPath = libDir.canonicalizePath(file);
_urlMappings[name] = libPath;
SdkLibraryImpl library = SdkLibraryImpl(name);
library.path = libPath;
libraryMap.setLibrary(name, library);
}
/// Given the 'embedderYamls' from [EmbedderYamlLocator] check each one for
/// the top level key 'embedded_libs'. Under the 'embedded_libs' key are key
/// value pairs. Each key is a 'dart:' library uri and each value is a path
/// (relative to the directory containing `_embedder.yaml`) to a dart script
/// for the given library. For example:
///
/// embedded_libs:
/// 'dart:io': '../../sdk/io/io.dart'
///
/// If a key doesn't begin with `dart:` it is ignored.
void _processEmbedderYaml(Folder libDir, YamlMap map) {
YamlNode embeddedLibs = map[_EMBEDDED_LIB_MAP_KEY];
if (embeddedLibs is YamlMap) {
embeddedLibs.forEach((k, v) => _processEmbeddedLibs(k, v, libDir));
}
}
}
/// A Dart SDK installed in a specified directory. Typical Dart SDK layout is
/// something like...
///
/// dart-sdk/
/// bin/
/// dart[.exe] <-- VM
/// lib/
/// core/
/// core.dart
/// ... other core library files ...
/// ... other libraries ...
/// util/
/// ... Dart utilities ...
/// Chromium/ <-- Dartium typically exists in a sibling directory
class FolderBasedDartSdk extends AbstractDartSdk {
/// The name of the directory within the SDK directory that contains
/// executables.
static const String _BIN_DIRECTORY_NAME = "bin";
/// The name of the directory within the SDK directory that contains
/// documentation for the libraries.
static const String _DOCS_DIRECTORY_NAME = "docs";
/// The name of the directory within the SDK directory that contains the
/// sdk_library_metadata directory.
static const String _INTERNAL_DIR = "_internal";
/// The name of the sdk_library_metadata directory that contains the package
/// holding the libraries.dart file.
static const String _SDK_LIBRARY_METADATA_DIR = "sdk_library_metadata";
/// The name of the directory within the sdk_library_metadata that contains
/// libraries.dart.
static const String _SDK_LIBRARY_METADATA_LIB_DIR = "lib";
/// The name of the directory within the SDK directory that contains the
/// libraries.
static const String _LIB_DIRECTORY_NAME = "lib";
/// The name of the libraries file.
static const String _LIBRARIES_FILE = "libraries.dart";
/// The name of the pub executable on windows.
static const String _PUB_EXECUTABLE_NAME_WIN = "pub.bat";
/// The name of the pub executable on non-windows operating systems.
static const String _PUB_EXECUTABLE_NAME = "pub";
/// The name of the file within the SDK directory that contains the version
/// number of the SDK.
static const String _VERSION_FILE_NAME = "version";
/// The directory containing the SDK.
final Folder _sdkDirectory;
/// The directory within the SDK directory that contains the libraries.
Folder? _libraryDirectory;
/// The revision number of this SDK, or `"0"` if the revision number cannot be
/// discovered.
String? _sdkVersion;
/// The cached language version of this SDK.
Version? _languageVersion;
/// The file containing the pub executable.
File? _pubExecutable;
/// Initialize a newly created SDK to represent the Dart SDK installed in the
/// [sdkDirectory].
FolderBasedDartSdk(ResourceProvider resourceProvider, Folder sdkDirectory)
: _sdkDirectory = sdkDirectory {
this.resourceProvider = resourceProvider;
libraryMap = initialLibraryMap();
}
@override
String get allowedExperimentsJson {
return _sdkDirectory
.getChildAssumingFolder('lib')
.getChildAssumingFolder('_internal')
.getChildAssumingFile('allowed_experiments.json')
.readAsStringSync();
}
/// Return the directory containing the SDK.
Folder get directory => _sdkDirectory;
/// Return the directory containing documentation for the SDK.
Folder get docDirectory =>
_sdkDirectory.getChildAssumingFolder(_DOCS_DIRECTORY_NAME);
@override
Version get languageVersion {
if (_languageVersion == null) {
var sdkVersionStr = _sdkDirectory
.getChildAssumingFile(_VERSION_FILE_NAME)
.readAsStringSync();
_languageVersion = languageVersionFromSdkVersion(sdkVersionStr);
}
return _languageVersion!;
}
/// Return the directory within the SDK directory that contains the libraries.
Folder get libraryDirectory {
return _libraryDirectory ??=
_sdkDirectory.getChildAssumingFolder(_LIB_DIRECTORY_NAME);
}
/// Return the file containing the Pub executable, or `null` if it does not
/// exist.
File get pubExecutable {
return _pubExecutable ??= _sdkDirectory
.getChildAssumingFolder(_BIN_DIRECTORY_NAME)
.getChildAssumingFile(OSUtilities.isWindows()
? _PUB_EXECUTABLE_NAME_WIN
: _PUB_EXECUTABLE_NAME);
}
/// Return the revision number of this SDK, or `"0"` if the revision number
/// cannot be discovered.
@override
String get sdkVersion {
if (_sdkVersion == null) {
File revisionFile =
_sdkDirectory.getChildAssumingFile(_VERSION_FILE_NAME);
try {
String revision = revisionFile.readAsStringSync();
_sdkVersion = revision.trim();
} on FileSystemException {
return _sdkVersion = DartSdk.DEFAULT_VERSION;
}
}
return _sdkVersion!;
}
/// Determine the search order for trying to locate the [_LIBRARIES_FILE].
Iterable<File> get _libraryMapLocations sync* {
yield libraryDirectory
.getChildAssumingFolder(_INTERNAL_DIR)
.getChildAssumingFolder(_SDK_LIBRARY_METADATA_DIR)
.getChildAssumingFolder(_SDK_LIBRARY_METADATA_LIB_DIR)
.getChildAssumingFile(_LIBRARIES_FILE);
yield libraryDirectory
.getChildAssumingFolder(_INTERNAL_DIR)
.getChildAssumingFile(_LIBRARIES_FILE);
}
/// Return info for debugging https://github.com/dart-lang/sdk/issues/35226.
@override
Map<String, Object> debugInfo() {
var result = super.debugInfo();
result['directory'] = _sdkDirectory.path;
return result;
}
@override
String? getRelativePathFromFile(File file) {
String filePath = file.path;
String libPath = libraryDirectory.path;
if (!filePath.startsWith("$libPath$separator")) {
return null;
}
return filePath.substring(libPath.length + 1);
}
/// Read all of the configuration files to initialize the library maps.
/// Return the initialized library map.
LibraryMap initialLibraryMap() {
List<String> searchedPaths = <String>[];
late StackTrace lastStackTrace;
late Object lastException;
for (File librariesFile in _libraryMapLocations) {
try {
String contents = librariesFile.readAsStringSync();
return SdkLibrariesReader().readFromFile(librariesFile, contents);
} catch (exception, stackTrace) {
print('[exception: $exception][stackTrace: $stackTrace]');
searchedPaths.add(librariesFile.path);
lastException = exception;
lastStackTrace = stackTrace;
}
}
StringBuffer buffer = StringBuffer();
buffer.writeln('Could not initialize the library map from $searchedPaths');
if (resourceProvider is MemoryResourceProvider) {
(resourceProvider as MemoryResourceProvider).writeOn(buffer);
}
// TODO(39284): should this exception be silent?
AnalysisEngine.instance.instrumentationService.logException(
SilentException(buffer.toString(), lastException, lastStackTrace));
return LibraryMap();
}
@override
Source? internalMapDartUri(String dartUri) {
String libraryName;
String relativePath;
int index = dartUri.indexOf('/');
if (index >= 0) {
libraryName = dartUri.substring(0, index);
relativePath = dartUri.substring(index + 1);
} else {
libraryName = dartUri;
relativePath = "";
}
SdkLibrary? library = getSdkLibrary(libraryName);
if (library == null) {
return null;
}
try {
File file = libraryDirectory.getChildAssumingFile(library.path);
if (relativePath.isNotEmpty) {
File relativeFile = file.parent.getChildAssumingFile(relativePath);
if (relativeFile.path == file.path) {
// The relative file is the library, so return a Source for the
// library rather than the part format.
return file.createSource(Uri.parse(library.shortName));
}
file = relativeFile;
}
return file.createSource(Uri.parse(dartUri));
} on FormatException {
return null;
}
}
}
/// An object used to read and parse the libraries file
/// (dart-sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart) for
/// information about the libraries in an SDK. The library information is
/// represented as a Dart file containing a single top-level variable whose
/// value is a const map. The keys of the map are the names of libraries defined
/// in the SDK and the values in the map are info objects defining the library.
/// For example, a subset of a typical SDK might have a libraries file that
/// looks like the following:
///
/// final Map<String, LibraryInfo> LIBRARIES = const <LibraryInfo> {
/// // Used by VM applications
/// "builtin" : const LibraryInfo(
/// "builtin/builtin_runtime.dart",
/// category: "Server",
/// platforms: VM_PLATFORM),
///
/// "compiler" : const LibraryInfo(
/// "compiler/compiler.dart",
/// category: "Tools",
/// platforms: 0),
/// };
class SdkLibrariesReader {
/// Return the library map read from the given [file], given that the content
/// of the file is already known to be [libraryFileContents].
LibraryMap readFromFile(File file, String libraryFileContents) =>
readFromSource(file.createSource(), libraryFileContents);
/// Return the library map read from the given [source], given that the
/// content of the file is already known to be [libraryFileContents].
LibraryMap readFromSource(Source source, String libraryFileContents) {
// TODO(paulberry): initialize the feature set appropriately based on the
// version of the SDK we are reading, and enable flags.
var featureSet = FeatureSet.latestLanguageVersion();
var parseResult = parseString(
content: libraryFileContents,
featureSet: featureSet,
throwIfDiagnostics: false,
path: source.fullName,
);
var unit = parseResult.unit;
var libraryBuilder = SdkLibrariesReader_LibraryBuilder();
if (parseResult.errors.isEmpty) {
unit.accept(libraryBuilder);
}
return libraryBuilder.librariesMap;
}
}