mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:13:04 +00:00
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:
parent
74deaad759
commit
6dd7744584
|
@ -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`.
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue