blob: bf8ac4b18f261c89d4841d2c6750831b7aa5d40d [file] [log] [blame]
// Copyright (c) 2017, 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.
library analyzer.src.generated.gn;
import 'dart:collection';
import 'dart:core';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/workspace.dart';
import 'package:path/path.dart';
/**
* Similar to Map#putIfAbsent, except that a value is stored only if it is not
* null.
*/
V _putIfNotNull<K, V>(Map<K, V> map, K key, V ifAbsent()) {
if (map.containsKey(key)) {
return map[key];
}
V computed = ifAbsent();
if (computed != null) {
map[key] = computed;
}
return computed;
}
/**
* The [UriResolver] used to resolve `file` URIs in a [GnWorkspace].
*/
class GnFileUriResolver extends ResourceUriResolver {
/**
* The workspace associated with this resolver.
*/
final GnWorkspace workspace;
/**
* Initialize a newly created resolver to be associated with the given
* [workspace].
*/
GnFileUriResolver(GnWorkspace workspace)
: workspace = workspace,
super(workspace.provider);
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
if (!ResourceUriResolver.isFileUri(uri)) {
return null;
}
String path = provider.pathContext.fromUri(uri);
File file = workspace.findFile(path);
if (file != null) {
return file.createSource(actualUri ?? uri);
}
return null;
}
}
/**
* The [UriResolver] used to resolve `package` URIs in a [GnWorkspace].
*/
class GnPackageUriResolver extends UriResolver {
/**
* The workspace associated with this resolver.
*/
final GnWorkspace workspace;
/**
* The path context that should be used to manipulate file system paths.
*/
final Context pathContext;
/**
* The cache of absolute [Uri]s to [Source]s mappings.
*/
final Map<Uri, Source> _sourceCache = new HashMap<Uri, Source>();
/**
* Initialize a newly created resolver to be associated with the given
* [workspace].
*/
GnPackageUriResolver(GnWorkspace workspace)
: workspace = workspace,
pathContext = workspace.provider.pathContext;
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
return _putIfNotNull(_sourceCache, uri, () {
if (uri.scheme != 'package') {
return null;
}
String uriPath = uri.path;
int slash = uriPath.indexOf('/');
// If the path either starts with a slash or has no slash, it is invalid.
if (slash < 1) {
return null;
}
String packageName = uriPath.substring(0, slash);
String fileUriPart = uriPath.substring(slash + 1);
String filePath = fileUriPart.replaceAll('/', pathContext.separator);
String packageBase = workspace.getPackageSource(packageName);
if (packageBase == null) {
return null;
}
String path = pathContext.join(packageBase, filePath);
File file = workspace.findFile(path);
return file?.createSource(uri);
});
}
@override
Uri restoreAbsolute(Source source) {
Context context = workspace.provider.pathContext;
String path = source.fullName;
if (!context.isWithin(workspace.root, path)) {
return null;
}
String package = workspace.packages.keys.firstWhere(
(key) => context.isWithin(workspace.packages[key], path),
orElse: () => null);
if (package == null) {
return null;
}
String sourcePath =
context.relative(path, from: workspace.packages[package]);
return Uri.parse('package:$package/$sourcePath');
}
}
/**
* Information about a Gn workspace.
*/
class GnWorkspace extends Workspace {
/**
* The name of the directory that identifies the root of the workspace.
*/
static const String _jiriRootName = '.jiri_root';
/**
* The resource provider used to access the file system.
*/
final ResourceProvider provider;
/**
* The absolute workspace root path (the directory containing the `.jiri_root`
* directory).
*/
final String root;
/**
* The path to the directory with source locations.
*
* Each file in this directory is named after a Dart package and contains the
* path to the package's sources.
*/
final String _packagesDirectoryPath;
/**
* The cache of package locations indexed by package name.
*/
final Map<String, String> _packageCache = new HashMap<String, String>();
GnWorkspace._internal(this.provider, this.root, this._packagesDirectoryPath);
factory GnWorkspace._(
ResourceProvider provider, String root, String packagesDirectoryPath) {
GnWorkspace workspace =
new GnWorkspace._internal(provider, root, packagesDirectoryPath);
// Preload known packages.
provider
.getFolder(packagesDirectoryPath)
.getChildren()
.where((resource) => resource is File)
.map((resource) => resource as File)
.forEach((file) {
String packageName = basename(file.path);
workspace.getPackageSource(packageName);
});
return workspace;
}
Map<String, String> get packages => _packageCache;
@override
Map<String, List<Folder>> get packageMap {
Map<String, List<Folder>> result = new HashMap<String, List<Folder>>();
_packageCache.forEach((package, sourceDir) {
result[package] = [provider.getFolder(sourceDir)];
});
return result;
}
@override
UriResolver get packageUriResolver => new GnPackageUriResolver(this);
@override
SourceFactory createSourceFactory(DartSdk sdk) {
List<UriResolver> resolvers = <UriResolver>[];
if (sdk != null) {
resolvers.add(new DartUriResolver(sdk));
}
resolvers.add(packageUriResolver);
resolvers.add(new GnFileUriResolver(this));
return new SourceFactory(resolvers, null, provider);
}
/**
* Return the source directory for the given package, or null if it could not
* be found.
*/
String getPackageSource(String packageName) {
return _putIfNotNull(_packageCache, packageName, () {
String path =
provider.pathContext.join(_packagesDirectoryPath, packageName);
return findFile(path)?.readAsStringSync();
});
}
/**
* Return the file with the given [absolutePath].
*
* Return `null` if the given [absolutePath] is not in the workspace [root].
*/
File findFile(String absolutePath) {
try {
File writableFile = provider.getFile(absolutePath);
if (writableFile.exists) {
return writableFile;
}
} catch (_) {}
return null;
}
/**
* Locate the Dart sources directory.
*
* Return `null` if it could not be found.
*/
static String _getPackagesDirectory(ResourceProvider provider, String root) {
Context pathContext = provider.pathContext;
String outDirectory = provider
.getFolder(pathContext.join(root, 'out'))
.getChildren()
.where((resource) => resource is Folder)
.map((resource) => resource as Folder)
.firstWhere((Folder folder) {
String baseName = basename(folder.path);
// TODO(pylaligand): find a better way to locate the proper directory.
return baseName.startsWith('debug') || baseName.startsWith('release');
}, orElse: () => null)?.path;
return outDirectory == null
? null
: provider.pathContext.join(outDirectory, 'gen', 'dart.sources');
}
/**
* Find the Gn workspace that contains the given [path].
*
* Return `null` if a workspace markers, such as the `.jiri_root` directory
* cannot be found.
*/
static GnWorkspace find(ResourceProvider provider, String path) {
Context context = provider.pathContext;
// Ensure that the path is absolute and normalized.
if (!context.isAbsolute(path)) {
throw new ArgumentError('not absolute: $path');
}
path = context.normalize(path);
Folder folder = provider.getFolder(path);
while (true) {
Folder parent = folder.parent;
if (parent == null) {
return null;
}
// Found the .jiri_root file, must be a non-git workspace.
if (folder.getChildAssumingFolder(_jiriRootName).exists) {
String root = folder.path;
String packagesDirectory = _getPackagesDirectory(provider, root);
return new GnWorkspace._(provider, root, packagesDirectory);
}
// Go up the folder.
folder = parent;
}
}
}