Index: pkg/analysis_server/lib/src/context_manager.dart |
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart |
index 51c42d29c85938cbfd68b7131c3ee0b8235bf761..fb1c3b4f5976cff18bd9f68d078ed152c813840c 100644 |
--- a/pkg/analysis_server/lib/src/context_manager.dart |
+++ b/pkg/analysis_server/lib/src/context_manager.dart |
@@ -6,6 +6,7 @@ library context.directory.manager; |
import 'dart:async'; |
import 'dart:collection'; |
+import 'dart:convert'; |
import 'dart:core' hide Resource; |
import 'package:analysis_server/src/analysis_server.dart'; |
@@ -20,6 +21,9 @@ import 'package:analyzer/src/generated/engine.dart'; |
import 'package:analyzer/src/generated/java_io.dart'; |
import 'package:analyzer/src/generated/source.dart'; |
import 'package:analyzer/src/generated/source_io.dart'; |
+import 'package:package_config/packages.dart'; |
+import 'package:package_config/packages_file.dart' as pkgfile show parse; |
+import 'package:package_config/src/packages_impl.dart' show MapPackages; |
import 'package:path/path.dart' as pathos; |
import 'package:watcher/watcher.dart'; |
import 'package:yaml/yaml.dart'; |
@@ -29,6 +33,12 @@ import 'package:yaml/yaml.dart'; |
* folders that should correspond to analysis contexts. |
*/ |
abstract class AbstractContextManager implements ContextManager { |
+ |
+ /** |
+ * Temporary flag to hide WIP .packages support (DEP 5). |
+ */ |
+ static bool ENABLE_PACKAGESPEC_SUPPORT = false; |
+ |
/** |
* The name of the `lib` directory. |
*/ |
@@ -45,6 +55,11 @@ abstract class AbstractContextManager implements ContextManager { |
static const String PUBSPEC_NAME = 'pubspec.yaml'; |
/** |
+ * File name of package spec files. |
+ */ |
+ static const String PACKAGE_SPEC_NAME = '.packages'; |
+ |
+ /** |
* [_ContextInfo] object for each included directory in the most |
* recent successful call to [setRoots]. |
*/ |
@@ -113,7 +128,8 @@ abstract class AbstractContextManager implements ContextManager { |
/** |
* Create and return a new analysis context. |
*/ |
- AnalysisContext addContext(Folder folder, UriResolver packageUriResolver); |
+ AnalysisContext addContext( |
+ Folder folder, UriResolver packageUriResolver, Packages packages); |
/** |
* Called when the set of files associated with a context have changed (or |
@@ -170,14 +186,20 @@ abstract class AbstractContextManager implements ContextManager { |
// Do nothing. |
} |
- /// Sets the [ignorePatterns] for the context [folder]. |
- void setIgnorePatternsForContext(Folder folder, List<String> ignorePatterns) { |
- _ContextInfo info = _contexts[folder]; |
- if (info == null) { |
- return; |
+ @override |
+ bool isInAnalysisRoot(String path) { |
+ // check if excluded |
+ if (_isExcluded(path)) { |
+ return false; |
} |
- var pathFilter = info.pathFilter; |
- pathFilter.setIgnorePatterns(ignorePatterns); |
+ // check if in of the roots |
+ for (Folder root in _contexts.keys) { |
+ if (root.contains(path)) { |
+ return true; |
+ } |
+ } |
+ // no |
+ return false; |
} |
/// Process [options] for the context [folder]. |
@@ -200,22 +222,6 @@ abstract class AbstractContextManager implements ContextManager { |
} |
@override |
- bool isInAnalysisRoot(String path) { |
- // check if excluded |
- if (_isExcluded(path)) { |
- return false; |
- } |
- // check if in of the roots |
- for (Folder root in _contexts.keys) { |
- if (root.contains(path)) { |
- return true; |
- } |
- } |
- // no |
- return false; |
- } |
- |
- @override |
void refresh(List<Resource> roots) { |
// Destroy old contexts |
List<Folder> contextFolders = _contexts.keys.toList(); |
@@ -240,6 +246,16 @@ abstract class AbstractContextManager implements ContextManager { |
*/ |
void removeContext(Folder folder); |
+ /// Sets the [ignorePatterns] for the context [folder]. |
+ void setIgnorePatternsForContext(Folder folder, List<String> ignorePatterns) { |
+ _ContextInfo info = _contexts[folder]; |
+ if (info == null) { |
+ return; |
+ } |
+ var pathFilter = info.pathFilter; |
+ pathFilter.setIgnorePatterns(ignorePatterns); |
+ } |
+ |
@override |
void setRoots(List<String> includedPaths, List<String> excludedPaths, |
Map<String, String> packageRoots) { |
@@ -334,7 +350,7 @@ abstract class AbstractContextManager implements ContextManager { |
* Called when the package map for a context has changed. |
*/ |
void updateContextPackageUriResolver( |
- Folder contextFolder, UriResolver packageUriResolver); |
+ Folder contextFolder, UriResolver packageUriResolver, Packages packages); |
/** |
* Resursively adds all Dart and HTML files to the [changeSet]. |
@@ -431,6 +447,22 @@ abstract class AbstractContextManager implements ContextManager { |
info.dependencySubscriptions.clear(); |
} |
+ void _checkForPackagespecUpdate( |
+ String path, _ContextInfo info, Folder folder) { |
+ // Check to see if this is the .packages file for this context and if so, |
+ // update the context's source factory. |
+ if (pathContext.basename(path) == PACKAGE_SPEC_NAME && |
+ info.isPathToPackageDescription(path)) { |
+ File packagespec = resourceProvider.getFile(path); |
+ if (packagespec.exists) { |
+ Packages packages = _readPackagespec(packagespec); |
+ if (packages != null) { |
+ updateContextPackageUriResolver(folder, null, packages); |
+ } |
+ } |
+ } |
+ } |
+ |
/** |
* Compute the appropriate package URI resolver for [folder], and store |
* dependency information in [info]. Return `null` if no package map can |
@@ -511,9 +543,9 @@ abstract class AbstractContextManager implements ContextManager { |
* Create a new empty context associated with [folder]. |
*/ |
_ContextInfo _createContext( |
- Folder folder, File pubspecFile, List<_ContextInfo> children) { |
+ Folder folder, File packagespecFile, List<_ContextInfo> children) { |
_ContextInfo info = new _ContextInfo( |
- folder, pubspecFile, children, normalizedPackageRoots[folder.path]); |
+ folder, packagespecFile, children, normalizedPackageRoots[folder.path]); |
_contexts[folder] = info; |
var options = analysisOptionsProvider.getOptions(folder); |
processOptionsForContext(folder, options); |
@@ -521,8 +553,22 @@ abstract class AbstractContextManager implements ContextManager { |
_handleWatchEvent(folder, info, event); |
}); |
try { |
- UriResolver packageUriResolver = _computePackageUriResolver(folder, info); |
- info.context = addContext(folder, packageUriResolver); |
+ Packages packages; |
+ UriResolver packageUriResolver; |
+ |
+ if (ENABLE_PACKAGESPEC_SUPPORT) { |
+ // Try .packages first. |
+ if (pathos.basename(packagespecFile.path) == PACKAGE_SPEC_NAME) { |
+ packages = _readPackagespec(packagespecFile); |
+ } |
+ } |
+ |
+ // Next resort to a package uri resolver. |
+ if (packages == null) { |
+ packageUriResolver = _computePackageUriResolver(folder, info); |
+ } |
+ |
+ info.context = addContext(folder, packageUriResolver, packages); |
info.context.name = folder.path; |
} catch (_) { |
info.changeSubscription.cancel(); |
@@ -538,13 +584,13 @@ abstract class AbstractContextManager implements ContextManager { |
* created for them and excluded from the context associated with the |
* [folder]. |
* |
- * If [withPubspecOnly] is `true`, a context will be created only if there |
- * is a 'pubspec.yaml' file in the [folder]. |
+ * If [withPackageSpecOnly] is `true`, a context will be created only if there |
+ * is a 'pubspec.yaml' or '.packages' file in the [folder]. |
* |
- * Returns create pubspec-based contexts. |
+ * Returns created contexts. |
*/ |
- List<_ContextInfo> _createContexts(Folder folder, bool withPubspecOnly) { |
- // try to find subfolders with pubspec files |
+ List<_ContextInfo> _createContexts(Folder folder, bool withPackageSpecOnly) { |
+ // Try to find subfolders with pubspecs or .packages files. |
List<_ContextInfo> children = <_ContextInfo>[]; |
try { |
for (Resource child in folder.getChildren()) { |
@@ -556,20 +602,31 @@ abstract class AbstractContextManager implements ContextManager { |
// The directory either doesn't exist or cannot be read. Either way, there |
// are no subfolders that need to be added. |
} |
- // check whether there is a pubspec in the folder |
- File pubspecFile = folder.getChild(PUBSPEC_NAME); |
- if (pubspecFile.exists) { |
+ |
+ File packageSpec; |
+ |
+ if (ENABLE_PACKAGESPEC_SUPPORT) { |
+ // Start by looking for .packages. |
+ packageSpec = folder.getChild(PACKAGE_SPEC_NAME); |
+ } |
+ |
+ // Fall back to looking for a pubspec. |
+ if (packageSpec == null || !packageSpec.exists) { |
+ packageSpec = folder.getChild(PUBSPEC_NAME); |
+ } |
+ |
+ if (packageSpec.exists) { |
return <_ContextInfo>[ |
- _createContextWithSources(folder, pubspecFile, children) |
+ _createContextWithSources(folder, packageSpec, children) |
]; |
} |
- // no pubspec, done |
- if (withPubspecOnly) { |
+ // No packagespec? Done. |
+ if (withPackageSpecOnly) { |
return children; |
} |
- // OK, create a context without a pubspec |
+ // OK, create a context without a packagespec. |
return <_ContextInfo>[ |
- _createContextWithSources(folder, pubspecFile, children) |
+ _createContextWithSources(folder, packageSpec, children) |
]; |
} |
@@ -599,11 +656,11 @@ abstract class AbstractContextManager implements ContextManager { |
} |
/** |
- * Extract a new [pubspecFile]-based context from [oldInfo]. |
+ * Extract a new [packagespecFile]-based context from [oldInfo]. |
*/ |
- void _extractContext(_ContextInfo oldInfo, File pubspecFile) { |
- Folder newFolder = pubspecFile.parent; |
- _ContextInfo newInfo = _createContext(newFolder, pubspecFile, []); |
+ void _extractContext(_ContextInfo oldInfo, File packagespecFile) { |
+ Folder newFolder = packagespecFile.parent; |
+ _ContextInfo newInfo = _createContext(newFolder, packagespecFile, []); |
newInfo.parent = oldInfo; |
// prepare sources to extract |
Map<String, Source> extractedSources = new HashMap<String, Source>(); |
@@ -657,12 +714,46 @@ abstract class AbstractContextManager implements ContextManager { |
if (_isInPackagesDir(path, folder)) { |
return; |
} |
+ |
Resource resource = resourceProvider.getResource(path); |
- // pubspec was added in a sub-folder, extract a new context |
- if (_isPubspec(path) && info.isRoot && !info.isPubspec(path)) { |
- _extractContext(info, resource); |
- return; |
+ |
+ if (ENABLE_PACKAGESPEC_SUPPORT) { |
+ String directoryPath = pathContext.dirname(path); |
+ |
+ // Check to see if we need to create a new context. |
+ if (info.isRoot) { |
+ |
+ // Only create a new context if this is not the same directory |
+ // described by our info object. |
+ if (info.folder.path != directoryPath) { |
+ if (_isPubspec(path)) { |
+ // Check for a sibling .packages file. |
+ if (!resourceProvider.getFile( |
+ pathos.join(directoryPath, PACKAGE_SPEC_NAME)).exists) { |
+ _extractContext(info, resource); |
+ return; |
+ } |
+ } |
+ if (_isPackagespec(path)) { |
+ // Check for a sibling pubspec.yaml file. |
+ if (!resourceProvider |
+ .getFile(pathos.join(directoryPath, PUBSPEC_NAME)).exists) { |
+ _extractContext(info, resource); |
+ return; |
+ } |
+ } |
+ } |
+ } |
+ } else { |
+ // pubspec was added in a sub-folder, extract a new context |
+ if (_isPubspec(path) && |
+ info.isRoot && |
+ !info.isPathToPackageDescription(path)) { |
+ _extractContext(info, resource); |
+ return; |
+ } |
} |
+ |
// If the file went away and was replaced by a folder before we |
// had a chance to process the event, resource might be a Folder. In |
// that case don't add it. |
@@ -678,11 +769,41 @@ abstract class AbstractContextManager implements ContextManager { |
} |
break; |
case ChangeType.REMOVE: |
- // pubspec was removed, merge the context into its parent |
- if (info.isPubspec(path) && !info.isRoot) { |
- _mergeContext(info); |
- return; |
+ |
+ // If package spec info is removed, check to see if we can merge contexts. |
+ // Note that it's important to verify that there is NEITHER a .packages nor a |
+ // lingering pubspec.yaml before merging. |
+ if (!info.isRoot) { |
+ if (ENABLE_PACKAGESPEC_SUPPORT) { |
+ String directoryPath = pathContext.dirname(path); |
+ |
+ // Only merge if this is the same directory described by our info object. |
+ if (info.folder.path == directoryPath) { |
+ if (_isPubspec(path)) { |
+ // Check for a sibling .packages file. |
+ if (!resourceProvider.getFile( |
+ pathos.join(directoryPath, PACKAGE_SPEC_NAME)).exists) { |
+ _mergeContext(info); |
+ return; |
+ } |
+ } |
+ if (_isPackagespec(path)) { |
+ // Check for a sibling pubspec.yaml file. |
+ if (!resourceProvider |
+ .getFile(pathos.join(directoryPath, PUBSPEC_NAME)).exists) { |
+ _mergeContext(info); |
+ return; |
+ } |
+ } |
+ } |
+ } else { |
+ if (info.isPathToPackageDescription(path)) { |
+ _mergeContext(info); |
+ return; |
+ } |
+ } |
} |
+ |
List<Source> sources = info.context.getSourcesWithFullName(path); |
if (!sources.isEmpty) { |
ChangeSet changeSet = new ChangeSet(); |
@@ -705,6 +826,9 @@ abstract class AbstractContextManager implements ContextManager { |
break; |
} |
+ //TODO(pquitslund): find the right place for this |
+ _checkForPackagespecUpdate(path, info, folder); |
+ |
if (info.packageMapInfo != null && |
info.packageMapInfo.isChangedDependency(path, resourceProvider)) { |
_recomputePackageUriResolver(info); |
@@ -714,9 +838,7 @@ abstract class AbstractContextManager implements ContextManager { |
/** |
* Returns `true` if the given [path] is excluded by [excludedPaths]. |
*/ |
- bool _isExcluded(String path) { |
- return _isExcludedBy(excludedPaths, path); |
- } |
+ bool _isExcluded(String path) => _isExcludedBy(excludedPaths, path); |
/** |
* Returns `true` if the given [path] is excluded by [excludedPaths]. |
@@ -740,12 +862,10 @@ abstract class AbstractContextManager implements ContextManager { |
return pathParts.contains(PACKAGES_NAME); |
} |
- /** |
- * Returns `true` if the given absolute [path] is a pubspec file. |
- */ |
- bool _isPubspec(String path) { |
- return pathContext.basename(path) == PUBSPEC_NAME; |
- } |
+ bool _isPackagespec(String path) => |
+ pathContext.basename(path) == PACKAGE_SPEC_NAME; |
+ |
+ bool _isPubspec(String path) => pathContext.basename(path) == PUBSPEC_NAME; |
/** |
* Merges [info] context into its parent. |
@@ -766,6 +886,18 @@ abstract class AbstractContextManager implements ContextManager { |
} |
} |
+ Packages _readPackagespec(File specFile) { |
+ try { |
+ String contents = specFile.readAsStringSync(); |
+ Map<String, Uri> map = |
+ pkgfile.parse(UTF8.encode(contents), new Uri.file(specFile.path)); |
+ return new MapPackages(map); |
+ } catch (_) { |
+ //TODO(pquitslund): consider creating an error for the spec file. |
+ return null; |
+ } |
+ } |
+ |
/** |
* Recompute the package URI resolver for the context described by [info], |
* and update the client appropriately. |
@@ -777,7 +909,7 @@ abstract class AbstractContextManager implements ContextManager { |
// "pub list" is in progress is just going to get thrown away anyhow. |
UriResolver packageUriResolver = |
_computePackageUriResolver(info.folder, info); |
- updateContextPackageUriResolver(info.folder, packageUriResolver); |
+ updateContextPackageUriResolver(info.folder, packageUriResolver, null); |
} |
/** |
@@ -923,9 +1055,9 @@ class _ContextInfo { |
_ContextInfo parent; |
/** |
- * The `pubspec.yaml` file path for this context. |
+ * The package description file path for this context. |
*/ |
- String pubspecPath; |
+ String packageDescriptionPath; |
/** |
* Stream subscription we are using to watch the context's directory for |
@@ -958,10 +1090,11 @@ class _ContextInfo { |
*/ |
OptimizingPubPackageMapInfo packageMapInfo; |
- _ContextInfo(Folder folder, File pubspecFile, this.children, this.packageRoot) |
+ _ContextInfo( |
+ Folder folder, File packagespecFile, this.children, this.packageRoot) |
: folder = folder, |
pathFilter = new PathFilter(folder.path, null) { |
- pubspecPath = pubspecFile.path; |
+ packageDescriptionPath = packagespecFile.path; |
for (_ContextInfo child in children) { |
child.parent = this; |
} |
@@ -981,20 +1114,18 @@ class _ContextInfo { |
}); |
} |
- /// Returns `true` if [path] should be ignored. |
- bool ignored(String path) => pathFilter.ignored(path); |
- |
/** |
* Returns `true` if [resource] is excluded, as it is in one of the children. |
*/ |
- bool excludesResource(Resource resource) { |
- return excludes(resource.path); |
- } |
+ bool excludesResource(Resource resource) => excludes(resource.path); |
+ |
+ /// Returns `true` if [path] should be ignored. |
+ bool ignored(String path) => pathFilter.ignored(path); |
/** |
- * Returns `true` if [path] is the pubspec file of this context. |
+ * Returns `true` if [path] is the package description file for this context |
+ * (pubspec.yaml or .packages). |
*/ |
- bool isPubspec(String path) { |
- return path == pubspecPath; |
- } |
+ bool isPathToPackageDescription(String path) => |
+ path == packageDescriptionPath; |
} |