| // 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. |
| |
| library analyzer.src.generated.bazel; |
| |
| 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'; |
| |
| /** |
| * Instances of the class `BazelFileUriResolver` resolve `file` URI's by first |
| * resolving file uri's in the expected way, and then by looking in the |
| * corresponding generated directories. |
| */ |
| class BazelFileUriResolver extends ResourceUriResolver { |
| final BazelWorkspace workspace; |
| |
| BazelFileUriResolver(BazelWorkspace 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] that can resolve `package` URIs in [BazelWorkspace]. |
| */ |
| class BazelPackageUriResolver extends UriResolver { |
| final BazelWorkspace _workspace; |
| final Context _context; |
| |
| /** |
| * The cache of absolute [Uri]s to [Source]s mappings. |
| */ |
| final Map<Uri, Source> _sourceCache = new HashMap<Uri, Source>(); |
| |
| BazelPackageUriResolver(BazelWorkspace workspace) |
| : _workspace = workspace, |
| _context = workspace.provider.pathContext; |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| return _sourceCache.putIfAbsent(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('/', _context.separator); |
| |
| if (packageName.indexOf('.') == -1) { |
| String path = _context.join(_workspace.root, 'third_party', 'dart', |
| packageName, 'lib', filePath); |
| File file = _workspace.findFile(path); |
| return file?.createSource(uri); |
| } else { |
| String packagePath = packageName.replaceAll('.', _context.separator); |
| String path = |
| _context.join(_workspace.root, packagePath, 'lib', filePath); |
| File file = _workspace.findFile(path); |
| return file?.createSource(uri); |
| } |
| }); |
| } |
| |
| @override |
| Uri restoreAbsolute(Source source) { |
| Context context = _workspace.provider.pathContext; |
| String path = source.fullName; |
| |
| Uri restore(String root, String path) { |
| if (root != null && context.isWithin(root, path)) { |
| String relative = context.relative(path, from: root); |
| List<String> components = context.split(relative); |
| if (components.length > 4 && |
| components[0] == 'third_party' && |
| components[1] == 'dart' && |
| components[3] == 'lib') { |
| String packageName = components[2]; |
| String pathInLib = components.skip(4).join('/'); |
| return Uri.parse('package:$packageName/$pathInLib'); |
| } else { |
| for (int i = 2; i < components.length - 1; i++) { |
| String component = components[i]; |
| if (component == 'lib') { |
| String packageName = components.getRange(0, i).join('.'); |
| String pathInLib = components.skip(i + 1).join('/'); |
| return Uri.parse('package:$packageName/$pathInLib'); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| // Search in each root. |
| for (String root in [ |
| _workspace.bin, |
| _workspace.genfiles, |
| _workspace.readonly, |
| _workspace.root |
| ]) { |
| Uri uri = restore(root, path); |
| if (uri != null) { |
| return uri; |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| /** |
| * Information about a Bazel workspace. |
| */ |
| class BazelWorkspace extends Workspace { |
| static const String _WORKSPACE = 'WORKSPACE'; |
| static const String _READONLY = 'READONLY'; |
| |
| /** |
| * Default prefix for "-genfiles" and "-bin" that will be assumed if no build |
| * output symlinks are found. |
| */ |
| static const defaultSymlinkPrefix = 'bazel'; |
| |
| final ResourceProvider provider; |
| |
| /** |
| * The absolute workspace root path. |
| * |
| * It contains the `WORKSPACE` file or its parent contains the `READONLY` |
| * folder. |
| */ |
| final String root; |
| |
| /** |
| * The absolute path to the optional read only workspace root, in the |
| * `READONLY` folder if a git-based workspace, or `null`. |
| */ |
| final String readonly; |
| |
| /** |
| * The absolute path to the `bazel-bin` folder. |
| */ |
| final String bin; |
| |
| /** |
| * The absolute path to the `bazel-genfiles` folder. |
| */ |
| final String genfiles; |
| |
| BazelWorkspace._( |
| this.provider, this.root, this.readonly, this.bin, this.genfiles); |
| |
| @override |
| Map<String, List<Folder>> get packageMap => null; |
| |
| @override |
| UriResolver get packageUriResolver => new BazelPackageUriResolver(this); |
| |
| @override |
| SourceFactory createSourceFactory(DartSdk sdk) { |
| List<UriResolver> resolvers = <UriResolver>[]; |
| if (sdk != null) { |
| resolvers.add(new DartUriResolver(sdk)); |
| } |
| resolvers.add(packageUriResolver); |
| resolvers.add(new BazelFileUriResolver(this)); |
| return new SourceFactory(resolvers, null, provider); |
| } |
| |
| /** |
| * Return the file with the given [absolutePath], looking first into |
| * directories for generated files: `bazel-bin` and `bazel-genfiles`, and |
| * then into the workspace root. The file in the workspace root is returned |
| * even if it does not exist. Return `null` if the given [absolutePath] is |
| * not in the workspace [root]. |
| */ |
| File findFile(String absolutePath) { |
| Context context = provider.pathContext; |
| try { |
| String relative = context.relative(absolutePath, from: root); |
| // genfiles |
| if (genfiles != null) { |
| File file = provider.getFile(context.join(genfiles, relative)); |
| if (file.exists) { |
| return file; |
| } |
| } |
| // bin |
| if (bin != null) { |
| File file = provider.getFile(context.join(bin, relative)); |
| if (file.exists) { |
| return file; |
| } |
| } |
| // Writable |
| File writableFile = provider.getFile(absolutePath); |
| if (writableFile.exists) { |
| return writableFile; |
| } |
| // READONLY |
| if (readonly != null) { |
| File file = provider.getFile(context.join(readonly, relative)); |
| if (file.exists) { |
| return file; |
| } |
| } |
| // Not generated, return the default one. |
| return writableFile; |
| } catch (_) { |
| return null; |
| } |
| } |
| |
| /** |
| * Find the Bazel workspace that contains the given [path]. |
| * |
| * Return `null` if a workspace markers, such as the `WORKSPACE` file, or |
| * the sibling `READONLY` folder cannot be found. |
| * |
| * Return `null` if the workspace does not have `bazel-genfiles` or |
| * `blaze-genfiles` folders, so we don't know where to search generated files. |
| * |
| * Return `null` if there is a folder 'foo' with the sibling `READONLY` |
| * folder, but there is corresponding folder 'foo' in `READONLY`, i.e. the |
| * corresponding readonly workspace root. |
| */ |
| static BazelWorkspace 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 READONLY folder, might be a git-based workspace. |
| Folder readonlyFolder = parent.getChildAssumingFolder(_READONLY); |
| if (readonlyFolder.exists) { |
| String root = folder.path; |
| String readonlyRoot = |
| context.join(readonlyFolder.path, folder.shortName); |
| if (provider.getFolder(readonlyRoot).exists) { |
| String symlinkPrefix = _findSymlinkPrefix(provider, root); |
| if (symlinkPrefix != null) { |
| return new BazelWorkspace._( |
| provider, |
| root, |
| readonlyRoot, |
| context.join(root, '$symlinkPrefix-bin'), |
| context.join(root, '$symlinkPrefix-genfiles')); |
| } |
| } |
| } |
| |
| // Found the WORKSPACE file, must be a non-git workspace. |
| if (folder.getChildAssumingFile(_WORKSPACE).exists) { |
| String root = folder.path; |
| String symlinkPrefix = _findSymlinkPrefix(provider, root); |
| if (symlinkPrefix == null) { |
| return null; |
| } |
| return new BazelWorkspace._( |
| provider, |
| root, |
| null, |
| context.join(root, '$symlinkPrefix-bin'), |
| context.join(root, '$symlinkPrefix-genfiles')); |
| } |
| |
| // Go up the folder. |
| folder = parent; |
| } |
| } |
| |
| /** |
| * Return the symlink prefix for folders `X-bin` or `X-genfiles` by probing |
| * the internal `blaze-genfiles` and `bazel-genfiles`. Make a default |
| * assumption according to defaultSymlinkPrefix if neither of the folders |
| * exists. |
| */ |
| static String _findSymlinkPrefix(ResourceProvider provider, String root) { |
| Context context = provider.pathContext; |
| if (provider.getFolder(context.join(root, 'blaze-genfiles')).exists) { |
| return 'blaze'; |
| } |
| if (provider.getFolder(context.join(root, 'bazel-genfiles')).exists) { |
| return 'bazel'; |
| } |
| // Couldn't find it. Make a default assumption. |
| return defaultSymlinkPrefix; |
| } |
| } |