Unverified Commit 1d7afd9c authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Don't indefinitely persist file hashes, handle more error conditions (#43602)

parent 5f6ca683
...@@ -406,7 +406,7 @@ class BuildSystem { ...@@ -406,7 +406,7 @@ class BuildSystem {
environment.outputDir.createSync(recursive: true); environment.outputDir.createSync(recursive: true);
// Load file hash store from previous builds. // Load file hash store from previous builds.
final FileHashStore fileCache = FileHashStore(environment) final FileHashStore fileCache = FileHashStore(environment, fs)
..initialize(); ..initialize();
// Perform sanity checks on build. // Perform sanity checks on build.
......
...@@ -69,12 +69,12 @@ class FileHash { ...@@ -69,12 +69,12 @@ class FileHash {
/// operation, and persisted to cache in the root build directory. /// operation, and persisted to cache in the root build directory.
/// ///
/// The format of the file store is subject to change and not part of its API. /// The format of the file store is subject to change and not part of its API.
///
// TODO(jonahwilliams): find a better way to clear out old entries, perhaps
// track the last access or modification date?
class FileHashStore { class FileHashStore {
FileHashStore(this.environment); FileHashStore(this.environment, this.fileSystem) :
_cachePath = environment.buildDir.childFile(_kFileCache).path;
final FileSystem fileSystem;
final String _cachePath;
final Environment environment; final Environment environment;
final HashMap<String, String> previousHashes = HashMap<String, String>(); final HashMap<String, String> previousHashes = HashMap<String, String>();
final HashMap<String, String> currentHashes = HashMap<String, String>(); final HashMap<String, String> currentHashes = HashMap<String, String>();
...@@ -88,20 +88,33 @@ class FileHashStore { ...@@ -88,20 +88,33 @@ class FileHashStore {
/// Read file hashes from disk. /// Read file hashes from disk.
void initialize() { void initialize() {
printTrace('Initializing file store'); printTrace('Initializing file store');
if (!_cacheFile.existsSync()) { final File cacheFile = fileSystem.file(_cachePath);
if (!cacheFile.existsSync()) {
return;
}
List<int> data;
try {
data = cacheFile.readAsBytesSync();
} on FileSystemException catch (err) {
printError(
'Failed to read file store at ${cacheFile.path} due to $err.\n'
'Build artifacts will not be cached. Try clearing the cache directories '
'with "flutter clean"',
);
return; return;
} }
final List<int> data = _cacheFile.readAsBytesSync();
FileStorage fileStorage; FileStorage fileStorage;
try { try {
fileStorage = FileStorage.fromBuffer(data); fileStorage = FileStorage.fromBuffer(data);
} catch (err) { } catch (err) {
printTrace('Filestorage format changed'); printTrace('Filestorage format changed');
_cacheFile.deleteSync(); cacheFile.deleteSync();
return; return;
} }
if (fileStorage.version != _kVersion) { if (fileStorage.version != _kVersion) {
_cacheFile.deleteSync(); printTrace('file cache format updating, clearing old hashes.');
cacheFile.deleteSync();
return; return;
} }
for (FileHash fileHash in fileStorage.files) { for (FileHash fileHash in fileStorage.files) {
...@@ -113,15 +126,12 @@ class FileHashStore { ...@@ -113,15 +126,12 @@ class FileHashStore {
/// Persist file hashes to disk. /// Persist file hashes to disk.
void persist() { void persist() {
printTrace('Persisting file store'); printTrace('Persisting file store');
final File file = _cacheFile; final File cacheFile = fileSystem.file(_cachePath);
if (!file.existsSync()) { if (!cacheFile.existsSync()) {
file.createSync(recursive: true); cacheFile.createSync(recursive: true);
} }
final List<FileHash> fileHashes = <FileHash>[]; final List<FileHash> fileHashes = <FileHash>[];
for (MapEntry<String, String> entry in currentHashes.entries) { for (MapEntry<String, String> entry in currentHashes.entries) {
previousHashes[entry.key] = entry.value;
}
for (MapEntry<String, String> entry in previousHashes.entries) {
fileHashes.add(FileHash(entry.key, entry.value)); fileHashes.add(FileHash(entry.key, entry.value));
} }
final FileStorage fileStorage = FileStorage( final FileStorage fileStorage = FileStorage(
...@@ -129,7 +139,15 @@ class FileHashStore { ...@@ -129,7 +139,15 @@ class FileHashStore {
fileHashes, fileHashes,
); );
final Uint8List buffer = fileStorage.toBuffer(); final Uint8List buffer = fileStorage.toBuffer();
file.writeAsBytesSync(buffer); try {
cacheFile.writeAsBytesSync(buffer);
} on FileSystemException catch (err) {
printError(
'Failed to persist file store at ${cacheFile.path} due to $err.\n'
'Build artifacts will not be cached. Try clearing the cache directories '
'with "flutter clean"',
);
}
printTrace('Done persisting file store'); printTrace('Done persisting file store');
} }
...@@ -166,6 +184,4 @@ class FileHashStore { ...@@ -166,6 +184,4 @@ class FileHashStore {
resource.release(); resource.release();
} }
} }
File get _cacheFile => environment.buildDir.childFile(_kFileCache);
} }
...@@ -3,8 +3,11 @@ ...@@ -3,8 +3,11 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/file_hash_store.dart'; import 'package:flutter_tools/src/build_system/file_hash_store.dart';
import 'package:flutter_tools/src/globals.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
...@@ -25,7 +28,7 @@ void main() { ...@@ -25,7 +28,7 @@ void main() {
}); });
test('Initializes file cache', () => testbed.run(() { test('Initializes file cache', () => testbed.run(() {
final FileHashStore fileCache = FileHashStore(environment); final FileHashStore fileCache = FileHashStore(environment, fs);
fileCache.initialize(); fileCache.initialize();
fileCache.persist(); fileCache.persist();
...@@ -43,7 +46,7 @@ void main() { ...@@ -43,7 +46,7 @@ void main() {
final File file = fs.file('foo.dart') final File file = fs.file('foo.dart')
..createSync() ..createSync()
..writeAsStringSync('hello'); ..writeAsStringSync('hello');
final FileHashStore fileCache = FileHashStore(environment); final FileHashStore fileCache = FileHashStore(environment, fs);
fileCache.initialize(); fileCache.initialize();
await fileCache.hashFiles(<File>[file]); await fileCache.hashFiles(<File>[file]);
fileCache.persist(); fileCache.persist();
...@@ -56,7 +59,7 @@ void main() { ...@@ -56,7 +59,7 @@ void main() {
expect(fileStorage.files.single.path, file.path); expect(fileStorage.files.single.path, file.path);
final FileHashStore newFileCache = FileHashStore(environment); final FileHashStore newFileCache = FileHashStore(environment, fs);
newFileCache.initialize(); newFileCache.initialize();
expect(newFileCache.currentHashes, isEmpty); expect(newFileCache.currentHashes, isEmpty);
expect(newFileCache.previousHashes['foo.dart'], currentHash); expect(newFileCache.previousHashes['foo.dart'], currentHash);
...@@ -73,7 +76,7 @@ void main() { ...@@ -73,7 +76,7 @@ void main() {
final File file = fs.file('foo.dart') final File file = fs.file('foo.dart')
..createSync() ..createSync()
..writeAsStringSync('hello'); ..writeAsStringSync('hello');
final FileHashStore fileCache = FileHashStore(environment); final FileHashStore fileCache = FileHashStore(environment, fs);
fileCache.initialize(); fileCache.initialize();
environment.buildDir.deleteSync(recursive: true); environment.buildDir.deleteSync(recursive: true);
...@@ -83,7 +86,7 @@ void main() { ...@@ -83,7 +86,7 @@ void main() {
})); }));
test('handles hashing missing files', () => testbed.run(() async { test('handles hashing missing files', () => testbed.run(() async {
final FileHashStore fileCache = FileHashStore(environment); final FileHashStore fileCache = FileHashStore(environment, fs);
fileCache.initialize(); fileCache.initialize();
final List<File> results = await fileCache.hashFiles(<File>[fs.file('hello.dart')]); final List<File> results = await fileCache.hashFiles(<File>[fs.file('hello.dart')]);
...@@ -92,4 +95,45 @@ void main() { ...@@ -92,4 +95,45 @@ void main() {
expect(results.single.path, 'hello.dart'); expect(results.single.path, 'hello.dart');
expect(fileCache.currentHashes, isNot(contains(fs.path.absolute('hello.dart')))); expect(fileCache.currentHashes, isNot(contains(fs.path.absolute('hello.dart'))));
})); }));
test('handles failure to persist file cache', () => testbed.run(() async {
final BufferLogger bufferLogger = logger;
final FakeForwardingFileSystem fakeForwardingFileSystem = FakeForwardingFileSystem(fs);
final FileHashStore fileCache = FileHashStore(environment, fakeForwardingFileSystem);
final String cacheFile = environment.buildDir.childFile('.filecache').path;
final MockFile mockFile = MockFile();
when(mockFile.writeAsBytesSync(any)).thenThrow(const FileSystemException('Out of space!'));
when(mockFile.existsSync()).thenReturn(true);
fileCache.initialize();
fakeForwardingFileSystem.files[cacheFile] = mockFile;
fileCache.persist();
expect(bufferLogger.errorText, contains('Out of space!'));
}));
test('handles failure to restore file cache', () => testbed.run(() async {
final BufferLogger bufferLogger = logger;
final FakeForwardingFileSystem fakeForwardingFileSystem = FakeForwardingFileSystem(fs);
final FileHashStore fileCache = FileHashStore(environment, fakeForwardingFileSystem);
final String cacheFile = environment.buildDir.childFile('.filecache').path;
final MockFile mockFile = MockFile();
when(mockFile.readAsBytesSync()).thenThrow(const FileSystemException('Out of space!'));
when(mockFile.existsSync()).thenReturn(true);
fakeForwardingFileSystem.files[cacheFile] = mockFile;
fileCache.initialize();
expect(bufferLogger.errorText, contains('Out of space!'));
}));
}
class FakeForwardingFileSystem extends ForwardingFileSystem {
FakeForwardingFileSystem(FileSystem fileSystem) : super(fileSystem);
final Map<String, FileSystemEntity> files = <String, FileSystemEntity>{};
@override
File file(dynamic path) => files[path] ?? super.file(path);
} }
class MockFile extends Mock implements File {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment