Cache linked bundles for Bazel packages in files..

Also verify unlinked bundles consistency with the majorVersion.

R=brianwilkerson@google.com, paulberry@google.com
BUG=

Review URL: https://codereview.chromium.org/2386743002 .
This commit is contained in:
Konstantin Shcheglov 2016-09-30 13:40:41 -07:00
parent 74deaad759
commit 6dd7744584
2 changed files with 281 additions and 6 deletions

View file

@ -8,6 +8,7 @@ import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_collection.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/link.dart';
@ -98,10 +99,23 @@ class Package {
*/
class SummaryProvider {
final ResourceProvider provider;
final String tempFileName;
final GetOutputFolder getOutputFolder;
final Folder linkedCacheFolder;
final AnalysisContext context;
final PackageBundle sdkBundle;
/**
* If `true` (by default), then linking new bundles is allowed.
* Otherwise only using existing cached bundles can be used.
*/
final bool allowLinking;
/**
* See [PackageBundleAssembler.currentMajorVersion].
*/
final int majorVersion;
/**
* Mapping from bundle paths to corresponding [Package]s. The packages in
* the map were consistent with their constituent sources at the moment when
@ -119,7 +133,16 @@ class SummaryProvider {
*/
final Map<Package, _LinkNode> packageToNodeMap = {};
SummaryProvider(this.provider, this.getOutputFolder, AnalysisContext context)
SummaryProvider(
this.provider,
this.tempFileName,
this.getOutputFolder,
this.linkedCacheFolder,
AnalysisContext context,
{@visibleForTesting
this.allowLinking: true,
@visibleForTesting
this.majorVersion: PackageBundleAssembler.currentMajorVersion})
: context = context,
sdkBundle = context.sourceFactory.dartSdk?.getLinkedBundle();
@ -152,7 +175,15 @@ class SummaryProvider {
return null;
}
_link(nodes);
// Read existing cached linked bundles.
for (_LinkNode node in nodes) {
_readLinked(node);
}
// Link new packages, if allowed.
if (allowLinking) {
_link(nodes);
}
// Create successfully linked packages.
return nodes
@ -193,6 +224,17 @@ class SummaryProvider {
return hex.encode(hashBytes);
}
/**
* Return the name of the file for a linked bundle, in strong or spec mode.
*/
String _getLinkedName(String hash) {
if (context.analysisOptions.strongMode) {
return 'linked_$hash.ds';
} else {
return 'linked_spec_$hash.ds';
}
}
/**
* Return the node for the given [uri], or `null` if there is no unlinked
* bundle that contains [uri].
@ -304,6 +346,28 @@ class SummaryProvider {
});
List<int> bytes = assembler.assemble().toBuffer();
node.package._linked = new PackageBundle.fromBuffer(bytes);
_writeLinked(node, bytes);
}
}
}
/**
* Attempt to read the linked bundle that corresponds to the given [node]
* with all its transitive dependencies.
*/
void _readLinked(_LinkNode node) {
String hash = node.linkedHash;
if (!node.isReady && hash != null) {
String fileName = _getLinkedName(hash);
File file = linkedCacheFolder.getChildAssumingFile(fileName);
// Try to read from the file system.
if (file.exists) {
try {
List<int> bytes = file.readAsBytesSync();
node.package._linked = new PackageBundle.fromBuffer(bytes);
} on FileSystemException {
// Ignore file system exceptions.
}
}
}
}
@ -317,6 +381,10 @@ class SummaryProvider {
try {
List<int> bytes = file.readAsBytesSync();
PackageBundle bundle = new PackageBundle.fromBuffer(bytes);
// Check the major version.
if (bundle.majorVersion != majorVersion) {
return null;
}
// Check for consistency, and fail if it's not.
if (!_isUnlinkedBundleConsistent(bundle)) {
return null;
@ -326,6 +394,28 @@ class SummaryProvider {
} on FileSystemException {}
return null;
}
/**
* Atomically write the given [bytes] into the file in the [folder].
*/
void _writeAtomic(Folder folder, String fileName, List<int> bytes) {
String filePath = folder.getChildAssumingFile(fileName).path;
File tempFile = folder.getChildAssumingFile(tempFileName);
tempFile.writeAsBytesSync(bytes);
tempFile.renameSync(filePath);
}
/**
* If a new linked bundle was linked for the given [node], write the bundle
* into the memory cache and the file system.
*/
void _writeLinked(_LinkNode node, List<int> bytes) {
String hash = node.linkedHash;
if (hash != null) {
String fileName = _getLinkedName(hash);
_writeAtomic(linkedCacheFolder, fileName, bytes);
}
}
}
/**
@ -339,6 +429,7 @@ class _LinkNode {
Set<_LinkNode> transitiveDependencies;
List<_LinkNode> _dependencies;
String _linkedHash;
_LinkNode(this.linker, this.package);
@ -391,6 +482,58 @@ class _LinkNode {
*/
bool get isReady => package.linked != null || failed;
/**
* Return the hash string that corresponds to this linked bundle in the
* context of its SDK bundle and transitive dependencies. Return `null` if
* the hash computation fails, because for example the full transitive
* dependencies cannot computed.
*/
String get linkedHash {
if (_linkedHash == null && transitiveDependencies != null && !failed) {
ApiSignature signature = new ApiSignature();
// Add all unlinked API signatures.
List<String> signatures = <String>[];
signatures.add(linker.sdkBundle.apiSignature);
transitiveDependencies
.map((node) => node.package.unlinked.apiSignature)
.forEach(signatures.add);
signatures.sort();
signatures.forEach(signature.addString);
// Combine into a single hash.
appendDeclaredVariables(signature);
_linkedHash = signature.toHex();
}
return _linkedHash;
}
/**
* Append names and values of all referenced declared variables (even the
* ones without actually declared values) to the given [signature].
*/
void appendDeclaredVariables(ApiSignature signature) {
Set<String> nameSet = new Set<String>();
for (_LinkNode node in transitiveDependencies) {
for (UnlinkedUnit unit in node.package.unlinked.unlinkedUnits) {
for (UnlinkedImport import in unit.imports) {
for (UnlinkedConfiguration configuration in import.configurations) {
nameSet.add(configuration.name);
}
}
for (UnlinkedExportPublic export in unit.publicNamespace.exports) {
for (UnlinkedConfiguration configuration in export.configurations) {
nameSet.add(configuration.name);
}
}
}
}
List<String> sortedNameList = nameSet.toList()..sort();
signature.addInt(sortedNameList.length);
for (String name in sortedNameList) {
signature.addString(name);
signature.addString(linker.context.declaredVariables.get(name) ?? '');
}
}
/**
* Compute the set of existing transitive dependencies for this node.
* If any dependency cannot be resolved, then set [failed] to `true`.

View file

@ -15,6 +15,7 @@ import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/bazel_summary.dart';
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/summarize_ast.dart';
import 'package:analyzer/src/summary/summarize_elements.dart';
import 'package:analyzer/src/util/fast_uri.dart';
@ -42,8 +43,12 @@ class BazelResultProviderTest extends _BaseTest {
@override
void setUp() {
super.setUp();
provider = new BazelResultProvider(
new SummaryProvider(resourceProvider, _getOutputFolder, context));
provider = new BazelResultProvider(new SummaryProvider(
resourceProvider,
'_.temp',
_getOutputFolder,
resourceProvider.getFolder('/tmp/dart/bazel/linked'),
context));
}
test_failure_inconsistent_directDependency() {
@ -136,7 +141,44 @@ class SummaryProviderTest extends _BaseTest {
@override
void setUp() {
super.setUp();
manager = new SummaryProvider(resourceProvider, _getOutputFolder, context);
_createManager();
}
test_getLinkedPackages_cached() {
_setComponentFile('aaa', 'a.dart', 'class A {}');
_setComponentFile(
'bbb',
'b.dart',
r'''
import 'package:components.aaa/a.dart';
class B extends A {}
''');
_writeUnlinkedBundle('components.aaa');
_writeUnlinkedBundle('components.bbb');
Source source = _resolveUri('package:components.bbb/b.dart');
// Session 1.
// Create linked bundles and store them in files.
{
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, hasLength(2));
}
// Session 2.
// Recreate manager (with disabled linking) and ask again.
{
_createManager(allowLinking: false);
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, hasLength(2));
}
}
test_getLinkedPackages_cached_declaredVariables_export() {
_testImpl_getLinkedPackages_cached_declaredVariables('export');
}
test_getLinkedPackages_cached_declaredVariables_import() {
_testImpl_getLinkedPackages_cached_declaredVariables('import');
}
test_getLinkedPackages_null_inconsistent_directDependency() {
@ -307,7 +349,7 @@ class C extends B {}
expect(manager.getUnlinkedForUri(source2.uri), same(package));
}
test_getUnlinkedForUri_inconsistent() {
test_getUnlinkedForUri_inconsistent_fileContent() {
File file1 = _setComponentFile('aaa', 'a1.dart', 'class A1 {}');
_setComponentFile('aaa', 'a2.dart', 'class A2 {}');
_writeUnlinkedBundle('components.aaa');
@ -318,6 +360,96 @@ class C extends B {}
expect(manager.getUnlinkedForUri(source1.uri), isNull);
expect(manager.getUnlinkedForUri(source2.uri), isNull);
}
test_getUnlinkedForUri_inconsistent_majorVersion() {
_setComponentFile('aaa', 'a.dart', 'class A {}');
_writeUnlinkedBundle('components.aaa');
Source source = _resolveUri('package:components.aaa/a.dart');
// Create manager with a different major version.
// The unlinked bundle cannot be used.
_createManager(majorVersion: 12345);
Package package = manager.getUnlinkedForUri(source.uri);
expect(package, isNull);
}
void _createManager(
{bool allowLinking: true,
int majorVersion: PackageBundleAssembler.currentMajorVersion}) {
manager = new SummaryProvider(resourceProvider, '_.temp', _getOutputFolder,
resourceProvider.getFolder('/tmp/dart/bazel/linked'), context,
allowLinking: allowLinking, majorVersion: majorVersion);
}
void _testImpl_getLinkedPackages_cached_declaredVariables(
String importOrExport) {
_setComponentFile(
'aaa',
'user.dart',
'''
$importOrExport 'foo.dart'
if (dart.library.io) 'foo_io.dart'
if (dart.library.html) 'foo_html.dart';
''');
_setComponentFile('aaa', 'foo.dart', 'class B {}');
_setComponentFile('aaa', 'foo_io.dart', 'class B {}');
_setComponentFile('aaa', 'foo_dart.dart', 'class B {}');
_writeUnlinkedBundle('components.aaa');
Source source = _resolveUri('package:components.aaa/user.dart');
void _assertDependencyInUser(PackageBundle bundle, String shortName) {
for (var i = 0; i < bundle.linkedLibraryUris.length; i++) {
if (bundle.linkedLibraryUris[i].endsWith('user.dart')) {
LinkedLibrary library = bundle.linkedLibraries[i];
expect(library.dependencies.map((d) => d.uri),
unorderedEquals(['', 'dart:core', shortName]));
return;
}
}
fail('Not found user.dart in $bundle');
}
// Session 1.
// Create linked bundles and store them in files.
{
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, hasLength(1));
_assertDependencyInUser(packages.single.linked, 'foo.dart');
}
// Session 2.
// Recreate manager and don't allow it to perform new linking.
// Set a declared variable, which is not used in the package.
// We still can get the cached linked bundle.
{
context.declaredVariables.define('not.used.variable', 'baz');
_createManager(allowLinking: false);
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, hasLength(1));
_assertDependencyInUser(packages.single.linked, 'foo.dart');
}
// Session 3.
// Recreate manager and don't allow it to perform new linking.
// Set the value of a referenced declared variable.
// So, we cannot use the previously cached linked bundle.
{
context.declaredVariables.define('dart.library.io', 'does-not-matter');
_createManager(allowLinking: false);
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, isEmpty);
}
// Session 4.
// Enable new linking, and configure to use 'foo_html.dart'.
{
context.declaredVariables.define('dart.library.html', 'true');
_createManager(allowLinking: true);
List<Package> packages = manager.getLinkedPackages(source);
expect(packages, hasLength(1));
_assertDependencyInUser(packages.single.linked, 'foo_html.dart');
}
}
}
class _BaseTest extends AbstractContextTest {