blob: bcff683b9523349794b3bb0ef3a29c393cf4f559 [file] [log] [blame]
// Copyright (c) 2014, 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:convert';
import 'dart:core';
import 'dart:io' as io;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/source/package_map_provider.dart';
/**
* The function used to run pub list.
*/
typedef io.ProcessResult RunPubList(Folder folder);
/**
* Implementation of PackageMapProvider that operates by executing pub.
*/
class PubPackageMapProvider implements PackageMapProvider {
static const String PUB_LIST_COMMAND = 'list-package-dirs';
/**
* The name of the 'pubspec.lock' file, which we assume is the dependency
* in the event that [PUB_LIST_COMMAND] fails.
*/
static const String PUBSPEC_LOCK_NAME = 'pubspec.lock';
/**
* [ResourceProvider] that is used to create the [Folder]s that populate the
* package map.
*/
final ResourceProvider resourceProvider;
/**
* Sdk that we use to find the pub executable.
*/
final FolderBasedDartSdk sdk;
/**
* The function used to run pub list.
*/
RunPubList _runPubList;
/**
* Construct a new instance.
* A [RunPubList] implementation may be injected for testing
*/
PubPackageMapProvider(this.resourceProvider, this.sdk, [this._runPubList]) {
if (_runPubList == null) {
_runPubList = _runPubListDefault;
}
}
@override
PackageMapInfo computePackageMap(Folder folder) {
// If the pubspec.lock file does not exist, no need to run anything.
{
String lockPath = getPubspecLockPath(folder);
if (!resourceProvider.getFile(lockPath).exists) {
return computePackageMapError(folder);
}
}
// TODO(paulberry) make this asynchronous so that we can (a) do other
// analysis while it's in progress, and (b) time out if it takes too long
// to respond.
io.ProcessResult result;
try {
result = _runPubList(folder);
} on io.ProcessException catch (exception, stackTrace) {
AnalysisEngine.instance.logger.logInformation(
"Error running pub $PUB_LIST_COMMAND\n$exception\n$stackTrace");
}
if (result == null || result.exitCode != 0) {
String exitCode =
result != null ? 'exit code ${result.exitCode}' : 'null';
AnalysisEngine.instance.logger
.logInformation("pub $PUB_LIST_COMMAND failed: $exitCode");
return computePackageMapError(folder);
}
try {
PackageMapInfo packageMap =
parsePackageMap(json.decode(result.stdout), folder);
return packageMap;
} catch (exception, stackTrace) {
AnalysisEngine.instance.logger.logError(
"Malformed output from pub $PUB_LIST_COMMAND\n$exception\n$stackTrace");
}
return computePackageMapError(folder);
}
/**
* Create a PackageMapInfo object representing an error condition.
*/
PackageMapInfo computePackageMapError(Folder folder) {
// Even if an error occurs, we still need to know the dependencies, so that
// we'll know when to try running "pub list-package-dirs" again.
// Unfortunately, "pub list-package-dirs" doesn't tell us dependencies when
// an error occurs, so just assume there is one dependency, "pubspec.lock".
String lockPath = getPubspecLockPath(folder);
List<String> dependencies = <String>[lockPath];
return new PackageMapInfo(null, dependencies.toSet());
}
/**
* Return the path to the `pubspec.lock` file in the given [folder].
*/
String getPubspecLockPath(Folder folder) =>
resourceProvider.pathContext.join(folder.path, PUBSPEC_LOCK_NAME);
/**
* Decode the JSON output from pub into a package map. Paths in the
* output are considered relative to [folder].
*/
PackageMapInfo parsePackageMap(Map obj, Folder folder) {
// The output of pub looks like this:
// {
// "packages": {
// "foo": "path/to/foo",
// "bar": ["path/to/bar1", "path/to/bar2"],
// "myapp": "path/to/myapp", // self link is included
// },
// "input_files": [
// "path/to/myapp/pubspec.lock"
// ]
// }
Map<String, List<Folder>> packageMap = new HashMap<String, List<Folder>>();
Map packages = obj['packages'];
processPaths(String packageName, List paths) {
List<Folder> folders = <Folder>[];
for (var path in paths) {
if (path is String) {
Resource resource = folder.getChildAssumingFolder(path);
if (resource is Folder) {
folders.add(resource);
}
}
}
if (folders.isNotEmpty) {
packageMap[packageName] = folders;
}
}
packages.forEach((key, value) {
if (value is String) {
processPaths(key, [value]);
} else if (value is List) {
processPaths(key, value);
}
});
Set<String> dependencies = new Set<String>();
List inputFiles = obj['input_files'];
if (inputFiles != null) {
for (var path in inputFiles) {
if (path is String) {
dependencies.add(folder.canonicalizePath(path));
}
}
}
return new PackageMapInfo(packageMap, dependencies);
}
/**
* Run pub list to determine the packages and input files.
*/
io.ProcessResult _runPubListDefault(Folder folder) {
String executablePath = sdk.pubExecutable.path;
List<String> arguments = [PUB_LIST_COMMAND];
String workingDirectory = folder.path;
int subprocessId = AnalysisEngine.instance.instrumentationService
.logSubprocessStart(executablePath, arguments, workingDirectory);
io.ProcessResult result = io.Process
.runSync(executablePath, arguments, workingDirectory: workingDirectory);
AnalysisEngine.instance.instrumentationService.logSubprocessResult(
subprocessId, result.exitCode, result.stdout, result.stderr);
return result;
}
}