Commit 661ba7b1 authored by John McCutchan's avatar John McCutchan Committed by GitHub

Merge pull request #5046 from johnmccutchan/devfs_assets

DevFS asset support and deletion support
parents 1fe118fe b6644733
This diff is collapsed.
...@@ -9,23 +9,37 @@ import 'dart:io'; ...@@ -9,23 +9,37 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'asset.dart';
import 'globals.dart'; import 'globals.dart';
import 'observatory.dart'; import 'observatory.dart';
// A file that has been added to a DevFS. // A file that has been added to a DevFS.
class DevFSEntry { class DevFSEntry {
DevFSEntry(this.devicePath, this.file); DevFSEntry(this.devicePath, this.file)
: bundleEntry = null;
DevFSEntry.bundle(this.devicePath, AssetBundleEntry bundleEntry)
: bundleEntry = bundleEntry,
file = bundleEntry.file;
final String devicePath; final String devicePath;
final AssetBundleEntry bundleEntry;
final File file; final File file;
FileStat _fileStat; FileStat _fileStat;
// When we updated the DevFS, did we see this entry?
bool _wasSeen = false;
DateTime get lastModified => _fileStat?.modified; DateTime get lastModified => _fileStat?.modified;
bool get stillExists { bool get stillExists {
if (_isSourceEntry)
return true;
_stat(); _stat();
return _fileStat.type != FileSystemEntityType.NOT_FOUND; return _fileStat.type != FileSystemEntityType.NOT_FOUND;
} }
bool get isModified { bool get isModified {
if (_isSourceEntry)
return true;
if (_fileStat == null) { if (_fileStat == null) {
_stat(); _stat();
return true; return true;
...@@ -36,8 +50,18 @@ class DevFSEntry { ...@@ -36,8 +50,18 @@ class DevFSEntry {
} }
void _stat() { void _stat() {
if (_isSourceEntry)
return;
_fileStat = file.statSync(); _fileStat = file.statSync();
} }
bool get _isSourceEntry => file == null;
Future<List<int>> contentsAsBytes() async {
if (_isSourceEntry)
return bundleEntry.contentsAsBytes();
return file.readAsBytes();
}
} }
...@@ -46,6 +70,7 @@ abstract class DevFSOperations { ...@@ -46,6 +70,7 @@ abstract class DevFSOperations {
Future<Uri> create(String fsName); Future<Uri> create(String fsName);
Future<dynamic> destroy(String fsName); Future<dynamic> destroy(String fsName);
Future<dynamic> writeFile(String fsName, DevFSEntry entry); Future<dynamic> writeFile(String fsName, DevFSEntry entry);
Future<dynamic> deleteFile(String fsName, DevFSEntry entry);
Future<dynamic> writeSource(String fsName, Future<dynamic> writeSource(String fsName,
String devicePath, String devicePath,
String contents); String contents);
...@@ -74,7 +99,7 @@ class ServiceProtocolDevFSOperations implements DevFSOperations { ...@@ -74,7 +99,7 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
Future<dynamic> writeFile(String fsName, DevFSEntry entry) async { Future<dynamic> writeFile(String fsName, DevFSEntry entry) async {
List<int> bytes; List<int> bytes;
try { try {
bytes = await entry.file.readAsBytes(); bytes = await entry.contentsAsBytes();
} catch (e) { } catch (e) {
return e; return e;
} }
...@@ -91,6 +116,11 @@ class ServiceProtocolDevFSOperations implements DevFSOperations { ...@@ -91,6 +116,11 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
} }
} }
@override
Future<dynamic> deleteFile(String fsName, DevFSEntry entry) async {
// TODO(johnmccutchan): Add file deletion to the devFS protocol.
}
@override @override
Future<dynamic> writeSource(String fsName, Future<dynamic> writeSource(String fsName,
String devicePath, String devicePath,
...@@ -135,7 +165,11 @@ class DevFS { ...@@ -135,7 +165,11 @@ class DevFS {
return await _operations.destroy(fsName); return await _operations.destroy(fsName);
} }
Future<dynamic> update() async { Future<dynamic> update([AssetBundle bundle = null]) async {
// Mark all entries as not seen.
_entries.forEach((String path, DevFSEntry entry) {
entry._wasSeen = false;
});
printTrace('DevFS: Starting sync from $rootDirectory'); printTrace('DevFS: Starting sync from $rootDirectory');
// Send the root and lib directories. // Send the root and lib directories.
Directory directory = rootDirectory; Directory directory = rootDirectory;
...@@ -162,6 +196,27 @@ class DevFS { ...@@ -162,6 +196,27 @@ class DevFS {
} }
} }
} }
if (bundle != null) {
// Synchronize asset bundle.
for (AssetBundleEntry entry in bundle.entries) {
// We write the assets into 'build/flx' so that they are in the
// same location in DevFS and the iOS simulator.
final String devicePath = path.join('build/flx', entry.archivePath);
_syncBundleEntry(devicePath, entry);
}
}
// Handle deletions.
final List<String> toRemove = new List<String>();
_entries.forEach((String path, DevFSEntry entry) {
if (!entry._wasSeen) {
_deleteEntry(path, entry);
toRemove.add(path);
}
});
for (int i = 0; i < toRemove.length; i++) {
_entries.remove(toRemove[i]);
}
// Send the assets.
printTrace('DevFS: Waiting for sync of ${_pendingWrites.length} files ' printTrace('DevFS: Waiting for sync of ${_pendingWrites.length} files '
'to finish'); 'to finish');
await Future.wait(_pendingWrites); await Future.wait(_pendingWrites);
...@@ -175,6 +230,10 @@ class DevFS { ...@@ -175,6 +230,10 @@ class DevFS {
logger.flush(); logger.flush();
} }
void _deleteEntry(String path, DevFSEntry entry) {
_pendingWrites.add(_operations.deleteFile(fsName, entry));
}
void _syncFile(String devicePath, File file) { void _syncFile(String devicePath, File file) {
DevFSEntry entry = _entries[devicePath]; DevFSEntry entry = _entries[devicePath];
if (entry == null) { if (entry == null) {
...@@ -182,6 +241,7 @@ class DevFS { ...@@ -182,6 +241,7 @@ class DevFS {
entry = new DevFSEntry(devicePath, file); entry = new DevFSEntry(devicePath, file);
_entries[devicePath] = entry; _entries[devicePath] = entry;
} }
entry._wasSeen = true;
bool needsWrite = entry.isModified; bool needsWrite = entry.isModified;
if (needsWrite) { if (needsWrite) {
Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry); Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
...@@ -193,13 +253,29 @@ class DevFS { ...@@ -193,13 +253,29 @@ class DevFS {
} }
} }
bool _shouldIgnore(String path) { void _syncBundleEntry(String devicePath, AssetBundleEntry assetBundleEntry) {
DevFSEntry entry = _entries[devicePath];
if (entry == null) {
// New file.
entry = new DevFSEntry.bundle(devicePath, assetBundleEntry);
_entries[devicePath] = entry;
}
entry._wasSeen = true;
Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
if (pendingWrite != null) {
_pendingWrites.add(pendingWrite);
} else {
printTrace('DevFS: Failed to sync "$devicePath"');
}
}
bool _shouldIgnore(String devicePath) {
List<String> ignoredPrefixes = <String>['android/', List<String> ignoredPrefixes = <String>['android/',
'build/', 'build/',
'ios/', 'ios/',
'packages/analyzer']; 'packages/analyzer'];
for (String ignoredPrefix in ignoredPrefixes) { for (String ignoredPrefix in ignoredPrefixes) {
if (path.startsWith(ignoredPrefix)) if (devicePath.startsWith(ignoredPrefix))
return true; return true;
} }
return false; return false;
......
This diff is collapsed.
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert' show UTF8;
import 'dart:io'; import 'dart:io';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'asset.dart';
import 'base/process.dart'; import 'base/process.dart';
abstract class ZipBuilder { abstract class ZipBuilder {
...@@ -21,30 +21,13 @@ abstract class ZipBuilder { ...@@ -21,30 +21,13 @@ abstract class ZipBuilder {
ZipBuilder._(); ZipBuilder._();
List<ZipEntry> entries = <ZipEntry>[]; List<AssetBundleEntry> entries = <AssetBundleEntry>[];
void addEntry(ZipEntry entry) => entries.add(entry); void addEntry(AssetBundleEntry entry) => entries.add(entry);
void createZip(File outFile, Directory zipBuildDir); void createZip(File outFile, Directory zipBuildDir);
} }
class ZipEntry {
ZipEntry.fromFile(this.archivePath, File file) {
this._file = file;
}
ZipEntry.fromString(this.archivePath, String contents) {
this._contents = contents;
}
final String archivePath;
File _file;
String _contents;
bool get isStringEntry => _contents != null;
}
class _ArchiveZipBuilder extends ZipBuilder { class _ArchiveZipBuilder extends ZipBuilder {
_ArchiveZipBuilder() : super._(); _ArchiveZipBuilder() : super._();
...@@ -52,14 +35,9 @@ class _ArchiveZipBuilder extends ZipBuilder { ...@@ -52,14 +35,9 @@ class _ArchiveZipBuilder extends ZipBuilder {
void createZip(File outFile, Directory zipBuildDir) { void createZip(File outFile, Directory zipBuildDir) {
Archive archive = new Archive(); Archive archive = new Archive();
for (ZipEntry entry in entries) { for (AssetBundleEntry entry in entries) {
if (entry.isStringEntry) { List<int> data = entry.contentsAsBytes();
List<int> data = UTF8.encode(entry._contents);
archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data)); archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data));
} else {
List<int> data = entry._file.readAsBytesSync();
archive.addFile(new ArchiveFile(entry.archivePath, data.length, data));
}
} }
List<int> zipData = new ZipEncoder().encode(archive); List<int> zipData = new ZipEncoder().encode(archive);
...@@ -79,18 +57,11 @@ class _ZipToolBuilder extends ZipBuilder { ...@@ -79,18 +57,11 @@ class _ZipToolBuilder extends ZipBuilder {
zipBuildDir.deleteSync(recursive: true); zipBuildDir.deleteSync(recursive: true);
zipBuildDir.createSync(recursive: true); zipBuildDir.createSync(recursive: true);
for (ZipEntry entry in entries) { for (AssetBundleEntry entry in entries) {
if (entry.isStringEntry) { List<int> data = entry.contentsAsBytes();
List<int> data = UTF8.encode(entry._contents);
File file = new File(path.join(zipBuildDir.path, entry.archivePath)); File file = new File(path.join(zipBuildDir.path, entry.archivePath));
file.parent.createSync(recursive: true); file.parent.createSync(recursive: true);
file.writeAsBytesSync(data); file.writeAsBytesSync(data);
} else {
List<int> data = entry._file.readAsBytesSync();
File file = new File(path.join(zipBuildDir.path, entry.archivePath));
file.parent.createSync(recursive: true);
file.writeAsBytesSync(data);
}
} }
if (_getCompressedNames().isNotEmpty) { if (_getCompressedNames().isNotEmpty) {
...@@ -112,13 +83,13 @@ class _ZipToolBuilder extends ZipBuilder { ...@@ -112,13 +83,13 @@ class _ZipToolBuilder extends ZipBuilder {
Iterable<String> _getCompressedNames() { Iterable<String> _getCompressedNames() {
return entries return entries
.where((ZipEntry entry) => !entry.isStringEntry) .where((AssetBundleEntry entry) => !entry.isStringEntry)
.map((ZipEntry entry) => entry.archivePath); .map((AssetBundleEntry entry) => entry.archivePath);
} }
Iterable<String> _getStoredNames() { Iterable<String> _getStoredNames() {
return entries return entries
.where((ZipEntry entry) => entry.isStringEntry) .where((AssetBundleEntry entry) => entry.isStringEntry)
.map((ZipEntry entry) => entry.archivePath); .map((AssetBundleEntry entry) => entry.archivePath);
} }
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -18,6 +19,8 @@ void main() { ...@@ -18,6 +19,8 @@ void main() {
String basePath; String basePath;
MockDevFSOperations devFSOperations = new MockDevFSOperations(); MockDevFSOperations devFSOperations = new MockDevFSOperations();
DevFS devFS; DevFS devFS;
AssetBundle assetBundle = new AssetBundle();
assetBundle.entries.add(new AssetBundleEntry.fromString('a.txt', ''));
group('devfs', () { group('devfs', () {
testUsingContext('create local file system', () async { testUsingContext('create local file system', () async {
tempDir = Directory.systemTemp.createTempSync(); tempDir = Directory.systemTemp.createTempSync();
...@@ -38,8 +41,6 @@ void main() { ...@@ -38,8 +41,6 @@ void main() {
testUsingContext('modify existing file on local file system', () async { testUsingContext('modify existing file on local file system', () async {
File file = new File(path.join(basePath, filePath)); File file = new File(path.join(basePath, filePath));
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6]); file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6]);
});
testUsingContext('update dev file system', () async {
await devFS.update(); await devFS.update();
expect(devFSOperations.contains('writeFile test bar/foo.txt'), isTrue); expect(devFSOperations.contains('writeFile test bar/foo.txt'), isTrue);
}); });
...@@ -47,11 +48,29 @@ void main() { ...@@ -47,11 +48,29 @@ void main() {
File file = new File(path.join(basePath, filePath2)); File file = new File(path.join(basePath, filePath2));
await file.parent.create(recursive: true); await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]); file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
});
testUsingContext('update dev file system', () async {
await devFS.update(); await devFS.update();
expect(devFSOperations.contains('writeFile test foo/bar.txt'), isTrue); expect(devFSOperations.contains('writeFile test foo/bar.txt'), isTrue);
}); });
testUsingContext('delete a file from the local file system', () async {
File file = new File(path.join(basePath, filePath));
await file.delete();
await devFS.update();
expect(devFSOperations.contains('deleteFile test bar/foo.txt'), isTrue);
});
testUsingContext('add file in an asset bundle', () async {
await devFS.update(assetBundle);
expect(devFSOperations.contains('writeFile test build/flx/a.txt'), isTrue);
});
testUsingContext('add a file to the asset bundle', () async {
assetBundle.entries.add(new AssetBundleEntry.fromString('b.txt', ''));
await devFS.update(assetBundle);
expect(devFSOperations.contains('writeFile test build/flx/b.txt'), isTrue);
});
testUsingContext('delete a file from the asset bundle', () async {
assetBundle.entries.clear();
await devFS.update(assetBundle);
expect(devFSOperations.contains('deleteFile test build/flx/b.txt'), isTrue);
});
testUsingContext('delete dev file system', () async { testUsingContext('delete dev file system', () async {
await devFS.destroy(); await devFS.destroy();
}); });
......
...@@ -75,6 +75,8 @@ class MockDevFSOperations implements DevFSOperations { ...@@ -75,6 +75,8 @@ class MockDevFSOperations implements DevFSOperations {
final List<String> messages = new List<String>(); final List<String> messages = new List<String>();
bool contains(String match) { bool contains(String match) {
print('Checking for `$match` in:');
print(messages);
bool result = messages.contains(match); bool result = messages.contains(match);
messages.clear(); messages.clear();
return result; return result;
...@@ -96,6 +98,11 @@ class MockDevFSOperations implements DevFSOperations { ...@@ -96,6 +98,11 @@ class MockDevFSOperations implements DevFSOperations {
messages.add('writeFile $fsName ${entry.devicePath}'); messages.add('writeFile $fsName ${entry.devicePath}');
} }
@override
Future<dynamic> deleteFile(String fsName, DevFSEntry entry) async {
messages.add('deleteFile $fsName ${entry.devicePath}');
}
@override @override
Future<dynamic> writeSource(String fsName, Future<dynamic> writeSource(String fsName,
String devicePath, String devicePath,
......
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