mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Merge pull request #5046 from johnmccutchan/devfs_assets
DevFS asset support and deletion support
This commit is contained in:
commit
661ba7b1d1
418
packages/flutter_tools/lib/src/asset.dart
Normal file
418
packages/flutter_tools/lib/src/asset.dart
Normal file
@ -0,0 +1,418 @@
|
||||
// Copyright 2016 The Chromium Authors. 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:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:json_schema/json_schema.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'cache.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'globals.dart';
|
||||
|
||||
/// An entry in an asset bundle.
|
||||
class AssetBundleEntry {
|
||||
/// An entry backed by a File.
|
||||
AssetBundleEntry.fromFile(this.archivePath, this.file)
|
||||
: _contents = null;
|
||||
|
||||
/// An entry backed by a String.
|
||||
AssetBundleEntry.fromString(this.archivePath, this._contents)
|
||||
: file = null;
|
||||
|
||||
/// The path within the bundle.
|
||||
final String archivePath;
|
||||
|
||||
/// The payload.
|
||||
List<int> contentsAsBytes() {
|
||||
if (_contents != null) {
|
||||
return UTF8.encode(_contents);
|
||||
} else {
|
||||
return file.readAsBytesSync();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isStringEntry => _contents != null;
|
||||
|
||||
final File file;
|
||||
final String _contents;
|
||||
}
|
||||
|
||||
/// A bundle of assets.
|
||||
class AssetBundle {
|
||||
final Set<AssetBundleEntry> entries = new Set<AssetBundleEntry>();
|
||||
|
||||
static const String defaultManifestPath = 'flutter.yaml';
|
||||
static const String defaultWorkingDirPath = 'build/flx';
|
||||
static const String _kFontSetMaterial = 'material';
|
||||
static const String _kFontSetRoboto = 'roboto';
|
||||
|
||||
Future<int> build({String manifestPath: defaultManifestPath,
|
||||
String workingDirPath: defaultWorkingDirPath,
|
||||
bool includeRobotoFonts: true}) async {
|
||||
Object manifest = _loadFlutterYamlManifest(manifestPath);
|
||||
if (manifest != null) {
|
||||
int result = await _validateFlutterYamlManifest(manifest);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
Map<String, dynamic> manifestDescriptor = manifest;
|
||||
assert(manifestDescriptor != null);
|
||||
String assetBasePath = path.dirname(path.absolute(manifestPath));
|
||||
|
||||
final PackageMap packageMap =
|
||||
new PackageMap(path.join(assetBasePath, '.packages'));
|
||||
|
||||
Map<_Asset, List<_Asset>> assetVariants = _parseAssets(
|
||||
packageMap,
|
||||
manifestDescriptor,
|
||||
assetBasePath,
|
||||
excludeDirs: <String>[workingDirPath, path.join(assetBasePath, 'build')]
|
||||
);
|
||||
|
||||
if (assetVariants == null)
|
||||
return 1;
|
||||
|
||||
final bool usesMaterialDesign = (manifestDescriptor != null) &&
|
||||
manifestDescriptor['uses-material-design'];
|
||||
|
||||
for (_Asset asset in assetVariants.keys) {
|
||||
AssetBundleEntry assetEntry = _createAssetEntry(asset);
|
||||
if (assetEntry == null)
|
||||
return 1;
|
||||
entries.add(assetEntry);
|
||||
|
||||
for (_Asset variant in assetVariants[asset]) {
|
||||
AssetBundleEntry variantEntry = _createAssetEntry(variant);
|
||||
if (variantEntry == null)
|
||||
return 1;
|
||||
entries.add(variantEntry);
|
||||
}
|
||||
}
|
||||
|
||||
List<_Asset> materialAssets = <_Asset>[];
|
||||
if (usesMaterialDesign) {
|
||||
materialAssets.addAll(_getMaterialAssets(_kFontSetMaterial));
|
||||
if (includeRobotoFonts)
|
||||
materialAssets.addAll(_getMaterialAssets(_kFontSetRoboto));
|
||||
}
|
||||
for (_Asset asset in materialAssets) {
|
||||
AssetBundleEntry assetEntry = _createAssetEntry(asset);
|
||||
if (assetEntry == null)
|
||||
return 1;
|
||||
entries.add(assetEntry);
|
||||
}
|
||||
|
||||
entries.add(_createAssetManifest(assetVariants));
|
||||
|
||||
AssetBundleEntry fontManifest =
|
||||
_createFontManifest(manifestDescriptor, usesMaterialDesign, includeRobotoFonts);
|
||||
if (fontManifest != null)
|
||||
entries.add(fontManifest);
|
||||
|
||||
// TODO(ianh): Only do the following line if we've changed packages
|
||||
entries.add(await _obtainLicenses(packageMap, assetBasePath));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dump() {
|
||||
print('Dumping AssetBundle:');
|
||||
for (AssetBundleEntry entry in entries) {
|
||||
print(entry.archivePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Asset {
|
||||
_Asset({ this.base, String assetEntry, this.relativePath, this.source }) {
|
||||
this._assetEntry = assetEntry;
|
||||
}
|
||||
|
||||
String _assetEntry;
|
||||
|
||||
final String base;
|
||||
|
||||
/// The entry to list in the generated asset manifest.
|
||||
String get assetEntry => _assetEntry ?? relativePath;
|
||||
|
||||
/// Where the resource is on disk relative to [base].
|
||||
final String relativePath;
|
||||
|
||||
final String source;
|
||||
|
||||
File get assetFile {
|
||||
return new File(source != null ? '$base/$source' : '$base/$relativePath');
|
||||
}
|
||||
|
||||
bool get assetFileExists => assetFile.existsSync();
|
||||
|
||||
/// The delta between what the assetEntry is and the relativePath (e.g.,
|
||||
/// packages/flutter_gallery).
|
||||
String get symbolicPrefix {
|
||||
if (_assetEntry == null || _assetEntry == relativePath)
|
||||
return null;
|
||||
int index = _assetEntry.indexOf(relativePath);
|
||||
return index == -1 ? null : _assetEntry.substring(0, index);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'asset: $assetEntry';
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readMaterialFontsManifest() {
|
||||
String fontsPath = path.join(path.absolute(Cache.flutterRoot),
|
||||
'packages', 'flutter_tools', 'schema', 'material_fonts.yaml');
|
||||
|
||||
return loadYaml(new File(fontsPath).readAsStringSync());
|
||||
}
|
||||
|
||||
final Map<String, dynamic> _materialFontsManifest = _readMaterialFontsManifest();
|
||||
|
||||
List<Map<String, dynamic>> _getMaterialFonts(String fontSet) {
|
||||
return _materialFontsManifest[fontSet];
|
||||
}
|
||||
|
||||
List<_Asset> _getMaterialAssets(String fontSet) {
|
||||
List<_Asset> result = <_Asset>[];
|
||||
|
||||
for (Map<String, dynamic> family in _getMaterialFonts(fontSet)) {
|
||||
for (Map<String, dynamic> font in family['fonts']) {
|
||||
String assetKey = font['asset'];
|
||||
result.add(new _Asset(
|
||||
base: '${Cache.flutterRoot}/bin/cache/artifacts/material_fonts',
|
||||
source: path.basename(assetKey),
|
||||
relativePath: assetKey
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
|
||||
|
||||
/// Returns a AssetBundleEntry representing the license file.
|
||||
Future<AssetBundleEntry> _obtainLicenses(
|
||||
PackageMap packageMap,
|
||||
String assetBase
|
||||
) async {
|
||||
// Read the LICENSE file from each package in the .packages file,
|
||||
// splitting each one into each component license (so that we can
|
||||
// de-dupe if possible).
|
||||
// For the sky_engine package we assume each license starts with
|
||||
// package names. For the other packages we assume that each
|
||||
// license is raw.
|
||||
final Map<String, Set<String>> packageLicenses = <String, Set<String>>{};
|
||||
for (String packageName in packageMap.map.keys) {
|
||||
final Uri package = packageMap.map[packageName];
|
||||
if (package != null && package.scheme == 'file') {
|
||||
final File file = new File.fromUri(package.resolve('../LICENSE'));
|
||||
if (file.existsSync()) {
|
||||
final List<String> rawLicenses =
|
||||
(await file.readAsString()).split(_licenseSeparator);
|
||||
for (String rawLicense in rawLicenses) {
|
||||
String licenseText;
|
||||
List<String> packageNames;
|
||||
if (packageName == 'sky_engine') {
|
||||
final int split = rawLicense.indexOf('\n\n');
|
||||
if (split >= 0) {
|
||||
packageNames = rawLicense.substring(0, split).split('\n');
|
||||
licenseText = rawLicense.substring(split + 2);
|
||||
}
|
||||
}
|
||||
if (licenseText == null) {
|
||||
licenseText = rawLicense;
|
||||
packageNames = <String>[packageName];
|
||||
}
|
||||
packageLicenses.putIfAbsent(rawLicense, () => new Set<String>())
|
||||
..addAll(packageNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> combinedLicensesList = packageLicenses.keys.map(
|
||||
(String license) {
|
||||
List<String> packageNames = packageLicenses[license].toList()
|
||||
..sort();
|
||||
return packageNames.join('\n') + '\n\n' + license;
|
||||
}
|
||||
).toList();
|
||||
combinedLicensesList.sort();
|
||||
|
||||
final String combinedLicenses = combinedLicensesList.join(_licenseSeparator);
|
||||
|
||||
return new AssetBundleEntry.fromString('LICENSE', combinedLicenses);
|
||||
}
|
||||
|
||||
|
||||
/// Create a [AssetBundleEntry] from the given [_Asset]; the asset must exist.
|
||||
AssetBundleEntry _createAssetEntry(_Asset asset) {
|
||||
assert(asset.assetFileExists);
|
||||
return new AssetBundleEntry.fromFile(asset.assetEntry, asset.assetFile);
|
||||
}
|
||||
|
||||
AssetBundleEntry _createAssetManifest(Map<_Asset, List<_Asset>> assetVariants) {
|
||||
Map<String, List<String>> json = <String, List<String>>{};
|
||||
for (_Asset main in assetVariants.keys) {
|
||||
List<String> variants = <String>[];
|
||||
for (_Asset variant in assetVariants[main])
|
||||
variants.add(variant.relativePath);
|
||||
json[main.relativePath] = variants;
|
||||
}
|
||||
return new AssetBundleEntry.fromString('AssetManifest.json', JSON.encode(json));
|
||||
}
|
||||
|
||||
AssetBundleEntry _createFontManifest(Map<String, dynamic> manifestDescriptor,
|
||||
bool usesMaterialDesign,
|
||||
bool includeRobotoFonts) {
|
||||
List<Map<String, dynamic>> fonts = <Map<String, dynamic>>[];
|
||||
if (usesMaterialDesign) {
|
||||
fonts.addAll(_getMaterialFonts(AssetBundle._kFontSetMaterial));
|
||||
if (includeRobotoFonts)
|
||||
fonts.addAll(_getMaterialFonts(AssetBundle._kFontSetRoboto));
|
||||
}
|
||||
if (manifestDescriptor != null && manifestDescriptor.containsKey('fonts'))
|
||||
fonts.addAll(manifestDescriptor['fonts']);
|
||||
if (fonts.isEmpty)
|
||||
return null;
|
||||
return new AssetBundleEntry.fromString('FontManifest.json', JSON.encode(fonts));
|
||||
}
|
||||
|
||||
/// Given an assetBase location and a flutter.yaml manifest, return a map of
|
||||
/// assets to asset variants.
|
||||
///
|
||||
/// Returns `null` on missing assets.
|
||||
Map<_Asset, List<_Asset>> _parseAssets(
|
||||
PackageMap packageMap,
|
||||
Map<String, dynamic> manifestDescriptor,
|
||||
String assetBase, {
|
||||
List<String> excludeDirs: const <String>[]
|
||||
}) {
|
||||
Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
|
||||
|
||||
if (manifestDescriptor == null)
|
||||
return result;
|
||||
|
||||
excludeDirs = excludeDirs.map(
|
||||
(String exclude) => path.absolute(exclude) + Platform.pathSeparator).toList();
|
||||
|
||||
if (manifestDescriptor.containsKey('assets')) {
|
||||
for (String asset in manifestDescriptor['assets']) {
|
||||
_Asset baseAsset = _resolveAsset(packageMap, assetBase, asset);
|
||||
|
||||
if (!baseAsset.assetFileExists) {
|
||||
printError('Error: unable to locate asset entry in flutter.yaml: "$asset".');
|
||||
return null;
|
||||
}
|
||||
|
||||
List<_Asset> variants = <_Asset>[];
|
||||
result[baseAsset] = variants;
|
||||
|
||||
// Find asset variants
|
||||
String assetPath = baseAsset.assetFile.path;
|
||||
String assetFilename = path.basename(assetPath);
|
||||
Directory assetDir = new Directory(path.dirname(assetPath));
|
||||
|
||||
List<FileSystemEntity> files = assetDir.listSync(recursive: true);
|
||||
|
||||
for (FileSystemEntity entity in files) {
|
||||
if (!FileSystemEntity.isFileSync(entity.path))
|
||||
continue;
|
||||
|
||||
// Exclude any files in the given directories.
|
||||
if (excludeDirs.any((String exclude) => entity.path.startsWith(exclude)))
|
||||
continue;
|
||||
|
||||
if (path.basename(entity.path) == assetFilename && entity.path != assetPath) {
|
||||
String key = path.relative(entity.path, from: baseAsset.base);
|
||||
String assetEntry;
|
||||
if (baseAsset.symbolicPrefix != null)
|
||||
assetEntry = path.join(baseAsset.symbolicPrefix, key);
|
||||
variants.add(new _Asset(base: baseAsset.base, assetEntry: assetEntry, relativePath: key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add assets referenced in the fonts section of the manifest.
|
||||
if (manifestDescriptor.containsKey('fonts')) {
|
||||
for (Map<String, dynamic> family in manifestDescriptor['fonts']) {
|
||||
List<Map<String, dynamic>> fonts = family['fonts'];
|
||||
if (fonts == null) continue;
|
||||
|
||||
for (Map<String, dynamic> font in fonts) {
|
||||
String asset = font['asset'];
|
||||
if (asset == null) continue;
|
||||
|
||||
_Asset baseAsset = _resolveAsset(packageMap, assetBase, asset);
|
||||
if (!baseAsset.assetFileExists) {
|
||||
printError('Error: unable to locate asset entry in flutter.yaml: "$asset".');
|
||||
return null;
|
||||
}
|
||||
|
||||
result[baseAsset] = <_Asset>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_Asset _resolveAsset(
|
||||
PackageMap packageMap,
|
||||
String assetBase,
|
||||
String asset
|
||||
) {
|
||||
if (asset.startsWith('packages/') && !FileSystemEntity.isFileSync(path.join(assetBase, asset))) {
|
||||
// Convert packages/flutter_gallery_assets/clouds-0.png to clouds-0.png.
|
||||
String packageKey = asset.substring(9);
|
||||
String relativeAsset = asset;
|
||||
|
||||
int index = packageKey.indexOf('/');
|
||||
if (index != -1) {
|
||||
relativeAsset = packageKey.substring(index + 1);
|
||||
packageKey = packageKey.substring(0, index);
|
||||
}
|
||||
|
||||
Uri uri = packageMap.map[packageKey];
|
||||
if (uri != null && uri.scheme == 'file') {
|
||||
File file = new File.fromUri(uri);
|
||||
return new _Asset(base: file.path, assetEntry: asset, relativePath: relativeAsset);
|
||||
}
|
||||
}
|
||||
|
||||
return new _Asset(base: assetBase, relativePath: asset);
|
||||
}
|
||||
|
||||
dynamic _loadFlutterYamlManifest(String manifestPath) {
|
||||
if (manifestPath == null || !FileSystemEntity.isFileSync(manifestPath))
|
||||
return null;
|
||||
String manifestDescriptor = new File(manifestPath).readAsStringSync();
|
||||
return loadYaml(manifestDescriptor);
|
||||
}
|
||||
|
||||
Future<int> _validateFlutterYamlManifest(Object manifest) async {
|
||||
String schemaPath = path.join(path.absolute(Cache.flutterRoot),
|
||||
'packages', 'flutter_tools', 'schema', 'flutter_yaml.json');
|
||||
Schema schema = await Schema.createSchemaFromUrl('file://$schemaPath');
|
||||
|
||||
Validator validator = new Validator(schema);
|
||||
if (validator.validate(manifest)) {
|
||||
return 0;
|
||||
} else {
|
||||
if (validator.errors.length == 1) {
|
||||
printError('Error in flutter.yaml: ${validator.errors.first}');
|
||||
} else {
|
||||
printError('Error in flutter.yaml:');
|
||||
printError(' ' + validator.errors.join('\n '));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -9,23 +9,37 @@ import 'dart:io';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'dart/package_map.dart';
|
||||
import 'asset.dart';
|
||||
import 'globals.dart';
|
||||
import 'observatory.dart';
|
||||
|
||||
// A file that has been added to a DevFS.
|
||||
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 AssetBundleEntry bundleEntry;
|
||||
|
||||
final File file;
|
||||
FileStat _fileStat;
|
||||
|
||||
// When we updated the DevFS, did we see this entry?
|
||||
bool _wasSeen = false;
|
||||
DateTime get lastModified => _fileStat?.modified;
|
||||
bool get stillExists {
|
||||
if (_isSourceEntry)
|
||||
return true;
|
||||
_stat();
|
||||
return _fileStat.type != FileSystemEntityType.NOT_FOUND;
|
||||
}
|
||||
bool get isModified {
|
||||
if (_isSourceEntry)
|
||||
return true;
|
||||
|
||||
if (_fileStat == null) {
|
||||
_stat();
|
||||
return true;
|
||||
@ -36,8 +50,18 @@ class DevFSEntry {
|
||||
}
|
||||
|
||||
void _stat() {
|
||||
if (_isSourceEntry)
|
||||
return;
|
||||
_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 {
|
||||
Future<Uri> create(String fsName);
|
||||
Future<dynamic> destroy(String fsName);
|
||||
Future<dynamic> writeFile(String fsName, DevFSEntry entry);
|
||||
Future<dynamic> deleteFile(String fsName, DevFSEntry entry);
|
||||
Future<dynamic> writeSource(String fsName,
|
||||
String devicePath,
|
||||
String contents);
|
||||
@ -74,7 +99,7 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
|
||||
Future<dynamic> writeFile(String fsName, DevFSEntry entry) async {
|
||||
List<int> bytes;
|
||||
try {
|
||||
bytes = await entry.file.readAsBytes();
|
||||
bytes = await entry.contentsAsBytes();
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
@ -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
|
||||
Future<dynamic> writeSource(String fsName,
|
||||
String devicePath,
|
||||
@ -135,7 +165,11 @@ class DevFS {
|
||||
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');
|
||||
// Send the root and lib directories.
|
||||
Directory directory = rootDirectory;
|
||||
@ -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 '
|
||||
'to finish');
|
||||
await Future.wait(_pendingWrites);
|
||||
@ -175,6 +230,10 @@ class DevFS {
|
||||
logger.flush();
|
||||
}
|
||||
|
||||
void _deleteEntry(String path, DevFSEntry entry) {
|
||||
_pendingWrites.add(_operations.deleteFile(fsName, entry));
|
||||
}
|
||||
|
||||
void _syncFile(String devicePath, File file) {
|
||||
DevFSEntry entry = _entries[devicePath];
|
||||
if (entry == null) {
|
||||
@ -182,6 +241,7 @@ class DevFS {
|
||||
entry = new DevFSEntry(devicePath, file);
|
||||
_entries[devicePath] = entry;
|
||||
}
|
||||
entry._wasSeen = true;
|
||||
bool needsWrite = entry.isModified;
|
||||
if (needsWrite) {
|
||||
Future<dynamic> pendingWrite = _operations.writeFile(fsName, entry);
|
||||
@ -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/',
|
||||
'build/',
|
||||
'ios/',
|
||||
'packages/analyzer'];
|
||||
for (String ignoredPrefix in ignoredPrefixes) {
|
||||
if (path.startsWith(ignoredPrefix))
|
||||
if (devicePath.startsWith(ignoredPrefix))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -3,16 +3,13 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:json_schema/json_schema.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'asset.dart';
|
||||
import 'base/file_system.dart' show ensureDirectoryExists;
|
||||
import 'base/process.dart';
|
||||
import 'cache.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'globals.dart';
|
||||
import 'toolchain.dart';
|
||||
@ -29,9 +26,6 @@ const String defaultWorkingDirPath = 'build/flx';
|
||||
|
||||
const String _kSnapshotKey = 'snapshot_blob.bin';
|
||||
|
||||
const String _kFontSetMaterial = 'material';
|
||||
const String _kFontSetRoboto = 'roboto';
|
||||
|
||||
Future<int> createSnapshot({
|
||||
String mainPath,
|
||||
String snapshotPath,
|
||||
@ -54,293 +48,6 @@ Future<int> createSnapshot({
|
||||
return runCommandAndStreamOutput(args);
|
||||
}
|
||||
|
||||
class _Asset {
|
||||
_Asset({ this.base, String assetEntry, this.relativePath, this.source }) {
|
||||
this._assetEntry = assetEntry;
|
||||
}
|
||||
|
||||
String _assetEntry;
|
||||
|
||||
final String base;
|
||||
|
||||
/// The entry to list in the generated asset manifest.
|
||||
String get assetEntry => _assetEntry ?? relativePath;
|
||||
|
||||
/// Where the resource is on disk relative to [base].
|
||||
final String relativePath;
|
||||
|
||||
final String source;
|
||||
|
||||
File get assetFile {
|
||||
return new File(source != null ? '$base/$source' : '$base/$relativePath');
|
||||
}
|
||||
|
||||
bool get assetFileExists => assetFile.existsSync();
|
||||
|
||||
/// The delta between what the assetEntry is and the relativePath (e.g.,
|
||||
/// packages/flutter_gallery).
|
||||
String get symbolicPrefix {
|
||||
if (_assetEntry == null || _assetEntry == relativePath)
|
||||
return null;
|
||||
int index = _assetEntry.indexOf(relativePath);
|
||||
return index == -1 ? null : _assetEntry.substring(0, index);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'asset: $assetEntry';
|
||||
}
|
||||
|
||||
Map<String, dynamic> _readMaterialFontsManifest() {
|
||||
String fontsPath = path.join(path.absolute(Cache.flutterRoot),
|
||||
'packages', 'flutter_tools', 'schema', 'material_fonts.yaml');
|
||||
|
||||
return loadYaml(new File(fontsPath).readAsStringSync());
|
||||
}
|
||||
|
||||
final Map<String, dynamic> _materialFontsManifest = _readMaterialFontsManifest();
|
||||
|
||||
List<Map<String, dynamic>> _getMaterialFonts(String fontSet) {
|
||||
return _materialFontsManifest[fontSet];
|
||||
}
|
||||
|
||||
List<_Asset> _getMaterialAssets(String fontSet) {
|
||||
List<_Asset> result = <_Asset>[];
|
||||
|
||||
for (Map<String, dynamic> family in _getMaterialFonts(fontSet)) {
|
||||
for (Map<String, dynamic> font in family['fonts']) {
|
||||
String assetKey = font['asset'];
|
||||
result.add(new _Asset(
|
||||
base: '${Cache.flutterRoot}/bin/cache/artifacts/material_fonts',
|
||||
source: path.basename(assetKey),
|
||||
relativePath: assetKey
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Given an assetBase location and a flutter.yaml manifest, return a map of
|
||||
/// assets to asset variants.
|
||||
///
|
||||
/// Returns `null` on missing assets.
|
||||
Map<_Asset, List<_Asset>> _parseAssets(
|
||||
PackageMap packageMap,
|
||||
Map<String, dynamic> manifestDescriptor,
|
||||
String assetBase, {
|
||||
List<String> excludeDirs: const <String>[]
|
||||
}) {
|
||||
Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
|
||||
|
||||
if (manifestDescriptor == null)
|
||||
return result;
|
||||
|
||||
excludeDirs = excludeDirs.map(
|
||||
(String exclude) => path.absolute(exclude) + Platform.pathSeparator).toList();
|
||||
|
||||
if (manifestDescriptor.containsKey('assets')) {
|
||||
for (String asset in manifestDescriptor['assets']) {
|
||||
_Asset baseAsset = _resolveAsset(packageMap, assetBase, asset);
|
||||
|
||||
if (!baseAsset.assetFileExists) {
|
||||
printError('Error: unable to locate asset entry in flutter.yaml: "$asset".');
|
||||
return null;
|
||||
}
|
||||
|
||||
List<_Asset> variants = <_Asset>[];
|
||||
result[baseAsset] = variants;
|
||||
|
||||
// Find asset variants
|
||||
String assetPath = baseAsset.assetFile.path;
|
||||
String assetFilename = path.basename(assetPath);
|
||||
Directory assetDir = new Directory(path.dirname(assetPath));
|
||||
|
||||
List<FileSystemEntity> files = assetDir.listSync(recursive: true);
|
||||
|
||||
for (FileSystemEntity entity in files) {
|
||||
if (!FileSystemEntity.isFileSync(entity.path))
|
||||
continue;
|
||||
|
||||
// Exclude any files in the given directories.
|
||||
if (excludeDirs.any((String exclude) => entity.path.startsWith(exclude)))
|
||||
continue;
|
||||
|
||||
if (path.basename(entity.path) == assetFilename && entity.path != assetPath) {
|
||||
String key = path.relative(entity.path, from: baseAsset.base);
|
||||
String assetEntry;
|
||||
if (baseAsset.symbolicPrefix != null)
|
||||
assetEntry = path.join(baseAsset.symbolicPrefix, key);
|
||||
variants.add(new _Asset(base: baseAsset.base, assetEntry: assetEntry, relativePath: key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add assets referenced in the fonts section of the manifest.
|
||||
if (manifestDescriptor.containsKey('fonts')) {
|
||||
for (Map<String, dynamic> family in manifestDescriptor['fonts']) {
|
||||
List<Map<String, dynamic>> fonts = family['fonts'];
|
||||
if (fonts == null) continue;
|
||||
|
||||
for (Map<String, dynamic> font in fonts) {
|
||||
String asset = font['asset'];
|
||||
if (asset == null) continue;
|
||||
|
||||
_Asset baseAsset = _resolveAsset(packageMap, assetBase, asset);
|
||||
if (!baseAsset.assetFileExists) {
|
||||
printError('Error: unable to locate asset entry in flutter.yaml: "$asset".');
|
||||
return null;
|
||||
}
|
||||
|
||||
result[baseAsset] = <_Asset>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
|
||||
|
||||
/// Returns a ZipEntry representing the license file.
|
||||
Future<ZipEntry> _obtainLicenses(
|
||||
PackageMap packageMap,
|
||||
String assetBase
|
||||
) async {
|
||||
// Read the LICENSE file from each package in the .packages file,
|
||||
// splitting each one into each component license (so that we can
|
||||
// de-dupe if possible).
|
||||
// For the sky_engine package we assume each license starts with
|
||||
// package names. For the other packages we assume that each
|
||||
// license is raw.
|
||||
final Map<String, Set<String>> packageLicenses = <String, Set<String>>{};
|
||||
for (String packageName in packageMap.map.keys) {
|
||||
final Uri package = packageMap.map[packageName];
|
||||
if (package != null && package.scheme == 'file') {
|
||||
final File file = new File.fromUri(package.resolve('../LICENSE'));
|
||||
if (file.existsSync()) {
|
||||
final List<String> rawLicenses = (await file.readAsString()).split(_licenseSeparator);
|
||||
for (String rawLicense in rawLicenses) {
|
||||
String licenseText;
|
||||
List<String> packageNames;
|
||||
if (packageName == 'sky_engine') {
|
||||
final int split = rawLicense.indexOf('\n\n');
|
||||
if (split >= 0) {
|
||||
packageNames = rawLicense.substring(0, split).split('\n');
|
||||
licenseText = rawLicense.substring(split + 2);
|
||||
}
|
||||
}
|
||||
if (licenseText == null) {
|
||||
licenseText = rawLicense;
|
||||
packageNames = <String>[packageName];
|
||||
}
|
||||
packageLicenses.putIfAbsent(rawLicense, () => new Set<String>())
|
||||
..addAll(packageNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> combinedLicensesList = packageLicenses.keys.map(
|
||||
(String license) {
|
||||
List<String> packageNames = packageLicenses[license].toList()
|
||||
..sort();
|
||||
return packageNames.join('\n') + '\n\n' + license;
|
||||
}
|
||||
).toList();
|
||||
combinedLicensesList.sort();
|
||||
|
||||
final String combinedLicenses = combinedLicensesList.join(_licenseSeparator);
|
||||
|
||||
return new ZipEntry.fromString('LICENSE', combinedLicenses);
|
||||
}
|
||||
|
||||
_Asset _resolveAsset(
|
||||
PackageMap packageMap,
|
||||
String assetBase,
|
||||
String asset
|
||||
) {
|
||||
if (asset.startsWith('packages/') && !FileSystemEntity.isFileSync(path.join(assetBase, asset))) {
|
||||
// Convert packages/flutter_gallery_assets/clouds-0.png to clouds-0.png.
|
||||
String packageKey = asset.substring(9);
|
||||
String relativeAsset = asset;
|
||||
|
||||
int index = packageKey.indexOf('/');
|
||||
if (index != -1) {
|
||||
relativeAsset = packageKey.substring(index + 1);
|
||||
packageKey = packageKey.substring(0, index);
|
||||
}
|
||||
|
||||
Uri uri = packageMap.map[packageKey];
|
||||
if (uri != null && uri.scheme == 'file') {
|
||||
File file = new File.fromUri(uri);
|
||||
return new _Asset(base: file.path, assetEntry: asset, relativePath: relativeAsset);
|
||||
}
|
||||
}
|
||||
|
||||
return new _Asset(base: assetBase, relativePath: asset);
|
||||
}
|
||||
|
||||
dynamic _loadManifest(String manifestPath) {
|
||||
if (manifestPath == null || !FileSystemEntity.isFileSync(manifestPath))
|
||||
return null;
|
||||
String manifestDescriptor = new File(manifestPath).readAsStringSync();
|
||||
return loadYaml(manifestDescriptor);
|
||||
}
|
||||
|
||||
Future<int> _validateManifest(Object manifest) async {
|
||||
String schemaPath = path.join(path.absolute(Cache.flutterRoot),
|
||||
'packages', 'flutter_tools', 'schema', 'flutter_yaml.json');
|
||||
Schema schema = await Schema.createSchemaFromUrl('file://$schemaPath');
|
||||
|
||||
Validator validator = new Validator(schema);
|
||||
if (validator.validate(manifest)) {
|
||||
return 0;
|
||||
} else {
|
||||
if (validator.errors.length == 1) {
|
||||
printError('Error in flutter.yaml: ${validator.errors.first}');
|
||||
} else {
|
||||
printError('Error in flutter.yaml:');
|
||||
printError(' ' + validator.errors.join('\n '));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [ZipEntry] from the given [_Asset]; the asset must exist.
|
||||
ZipEntry _createAssetEntry(_Asset asset) {
|
||||
assert(asset.assetFileExists);
|
||||
return new ZipEntry.fromFile(asset.assetEntry, asset.assetFile);
|
||||
}
|
||||
|
||||
ZipEntry _createAssetManifest(Map<_Asset, List<_Asset>> assetVariants) {
|
||||
Map<String, List<String>> json = <String, List<String>>{};
|
||||
for (_Asset main in assetVariants.keys) {
|
||||
List<String> variants = <String>[];
|
||||
for (_Asset variant in assetVariants[main])
|
||||
variants.add(variant.relativePath);
|
||||
json[main.relativePath] = variants;
|
||||
}
|
||||
return new ZipEntry.fromString('AssetManifest.json', JSON.encode(json));
|
||||
}
|
||||
|
||||
ZipEntry _createFontManifest(Map<String, dynamic> manifestDescriptor,
|
||||
bool usesMaterialDesign,
|
||||
bool includeRobotoFonts) {
|
||||
List<Map<String, dynamic>> fonts = <Map<String, dynamic>>[];
|
||||
if (usesMaterialDesign) {
|
||||
fonts.addAll(_getMaterialFonts(_kFontSetMaterial));
|
||||
if (includeRobotoFonts)
|
||||
fonts.addAll(_getMaterialFonts(_kFontSetRoboto));
|
||||
}
|
||||
if (manifestDescriptor != null && manifestDescriptor.containsKey('fonts'))
|
||||
fonts.addAll(manifestDescriptor['fonts']);
|
||||
if (fonts.isEmpty)
|
||||
return null;
|
||||
return new ZipEntry.fromString('FontManifest.json', JSON.encode(fonts));
|
||||
}
|
||||
|
||||
/// Build the flx in the build/ directory and return `localBundlePath` on success.
|
||||
///
|
||||
/// Return `null` on failure.
|
||||
@ -362,19 +69,6 @@ Future<String> buildFlx({
|
||||
return result == 0 ? localBundlePath : null;
|
||||
}
|
||||
|
||||
/// The result from [buildInTempDir]. Note that this object should be disposed after use.
|
||||
class DirectoryResult {
|
||||
DirectoryResult(this.directory, this.localBundlePath);
|
||||
|
||||
final Directory directory;
|
||||
final String localBundlePath;
|
||||
|
||||
/// Call this to delete the temporary directory.
|
||||
void dispose() {
|
||||
directory.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> build({
|
||||
String mainPath: defaultMainPath,
|
||||
String manifestPath: defaultManifestPath,
|
||||
@ -386,16 +80,6 @@ Future<int> build({
|
||||
bool precompiledSnapshot: false,
|
||||
bool includeRobotoFonts: true
|
||||
}) async {
|
||||
Object manifest = _loadManifest(manifestPath);
|
||||
if (manifest != null) {
|
||||
int result = await _validateManifest(manifest);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
Map<String, dynamic> manifestDescriptor = manifest;
|
||||
|
||||
String assetBasePath = path.dirname(path.absolute(manifestPath));
|
||||
|
||||
File snapshotFile;
|
||||
|
||||
if (!precompiledSnapshot) {
|
||||
@ -417,9 +101,8 @@ Future<int> build({
|
||||
}
|
||||
|
||||
return assemble(
|
||||
manifestDescriptor: manifestDescriptor,
|
||||
manifestPath: manifestPath,
|
||||
snapshotFile: snapshotFile,
|
||||
assetBasePath: assetBasePath,
|
||||
outputPath: outputPath,
|
||||
privateKeyPath: privateKeyPath,
|
||||
workingDirPath: workingDirPath,
|
||||
@ -428,9 +111,8 @@ Future<int> build({
|
||||
}
|
||||
|
||||
Future<int> assemble({
|
||||
Map<String, dynamic> manifestDescriptor: const <String, dynamic>{},
|
||||
String manifestPath,
|
||||
File snapshotFile,
|
||||
String assetBasePath: defaultAssetBasePath,
|
||||
String outputPath: defaultFlxOutputPath,
|
||||
String privateKeyPath: defaultPrivateKeyPath,
|
||||
String workingDirPath: defaultWorkingDirPath,
|
||||
@ -438,61 +120,22 @@ Future<int> assemble({
|
||||
}) async {
|
||||
printTrace('Building $outputPath');
|
||||
|
||||
final PackageMap packageMap = new PackageMap(path.join(assetBasePath, '.packages'));
|
||||
|
||||
Map<_Asset, List<_Asset>> assetVariants = _parseAssets(
|
||||
packageMap,
|
||||
manifestDescriptor,
|
||||
assetBasePath,
|
||||
excludeDirs: <String>[workingDirPath, path.join(assetBasePath, 'build')]
|
||||
);
|
||||
|
||||
if (assetVariants == null)
|
||||
return 1;
|
||||
|
||||
final bool usesMaterialDesign = manifestDescriptor != null &&
|
||||
manifestDescriptor['uses-material-design'] == true;
|
||||
// Build the asset bundle.
|
||||
AssetBundle assetBundle = new AssetBundle();
|
||||
int result = await assetBundle.build(manifestPath: manifestPath,
|
||||
workingDirPath: workingDirPath,
|
||||
includeRobotoFonts: includeRobotoFonts);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ZipBuilder zipBuilder = new ZipBuilder();
|
||||
|
||||
// Add all entries from the asset bundle.
|
||||
zipBuilder.entries.addAll(assetBundle.entries);
|
||||
|
||||
if (snapshotFile != null)
|
||||
zipBuilder.addEntry(new ZipEntry.fromFile(_kSnapshotKey, snapshotFile));
|
||||
|
||||
for (_Asset asset in assetVariants.keys) {
|
||||
ZipEntry assetEntry = _createAssetEntry(asset);
|
||||
if (assetEntry == null)
|
||||
return 1;
|
||||
zipBuilder.addEntry(assetEntry);
|
||||
|
||||
for (_Asset variant in assetVariants[asset]) {
|
||||
ZipEntry variantEntry = _createAssetEntry(variant);
|
||||
if (variantEntry == null)
|
||||
return 1;
|
||||
zipBuilder.addEntry(variantEntry);
|
||||
}
|
||||
}
|
||||
|
||||
List<_Asset> materialAssets = <_Asset>[];
|
||||
if (usesMaterialDesign) {
|
||||
materialAssets.addAll(_getMaterialAssets(_kFontSetMaterial));
|
||||
if (includeRobotoFonts)
|
||||
materialAssets.addAll(_getMaterialAssets(_kFontSetRoboto));
|
||||
}
|
||||
for (_Asset asset in materialAssets) {
|
||||
ZipEntry assetEntry = _createAssetEntry(asset);
|
||||
if (assetEntry == null)
|
||||
return 1;
|
||||
zipBuilder.addEntry(assetEntry);
|
||||
}
|
||||
|
||||
zipBuilder.addEntry(_createAssetManifest(assetVariants));
|
||||
|
||||
ZipEntry fontManifest = _createFontManifest(manifestDescriptor, usesMaterialDesign, includeRobotoFonts);
|
||||
if (fontManifest != null)
|
||||
zipBuilder.addEntry(fontManifest);
|
||||
|
||||
// TODO(ianh): Only do the following line if we've changed packages
|
||||
zipBuilder.addEntry(await _obtainLicenses(packageMap, assetBasePath));
|
||||
zipBuilder.addEntry(new AssetBundleEntry.fromFile(_kSnapshotKey, snapshotFile));
|
||||
|
||||
ensureDirectoryExists(outputPath);
|
||||
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert' show UTF8;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'asset.dart';
|
||||
import 'base/process.dart';
|
||||
|
||||
abstract class ZipBuilder {
|
||||
@ -21,30 +21,13 @@ abstract class 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);
|
||||
}
|
||||
|
||||
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 {
|
||||
_ArchiveZipBuilder() : super._();
|
||||
|
||||
@ -52,14 +35,9 @@ class _ArchiveZipBuilder extends ZipBuilder {
|
||||
void createZip(File outFile, Directory zipBuildDir) {
|
||||
Archive archive = new Archive();
|
||||
|
||||
for (ZipEntry entry in entries) {
|
||||
if (entry.isStringEntry) {
|
||||
List<int> data = UTF8.encode(entry._contents);
|
||||
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));
|
||||
}
|
||||
for (AssetBundleEntry entry in entries) {
|
||||
List<int> data = entry.contentsAsBytes();
|
||||
archive.addFile(new ArchiveFile.noCompress(entry.archivePath, data.length, data));
|
||||
}
|
||||
|
||||
List<int> zipData = new ZipEncoder().encode(archive);
|
||||
@ -79,18 +57,11 @@ class _ZipToolBuilder extends ZipBuilder {
|
||||
zipBuildDir.deleteSync(recursive: true);
|
||||
zipBuildDir.createSync(recursive: true);
|
||||
|
||||
for (ZipEntry entry in entries) {
|
||||
if (entry.isStringEntry) {
|
||||
List<int> data = UTF8.encode(entry._contents);
|
||||
File file = new File(path.join(zipBuildDir.path, entry.archivePath));
|
||||
file.parent.createSync(recursive: true);
|
||||
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);
|
||||
}
|
||||
for (AssetBundleEntry entry in entries) {
|
||||
List<int> data = entry.contentsAsBytes();
|
||||
File file = new File(path.join(zipBuildDir.path, entry.archivePath));
|
||||
file.parent.createSync(recursive: true);
|
||||
file.writeAsBytesSync(data);
|
||||
}
|
||||
|
||||
if (_getCompressedNames().isNotEmpty) {
|
||||
@ -112,13 +83,13 @@ class _ZipToolBuilder extends ZipBuilder {
|
||||
|
||||
Iterable<String> _getCompressedNames() {
|
||||
return entries
|
||||
.where((ZipEntry entry) => !entry.isStringEntry)
|
||||
.map((ZipEntry entry) => entry.archivePath);
|
||||
.where((AssetBundleEntry entry) => !entry.isStringEntry)
|
||||
.map((AssetBundleEntry entry) => entry.archivePath);
|
||||
}
|
||||
|
||||
Iterable<String> _getStoredNames() {
|
||||
return entries
|
||||
.where((ZipEntry entry) => entry.isStringEntry)
|
||||
.map((ZipEntry entry) => entry.archivePath);
|
||||
.where((AssetBundleEntry entry) => entry.isStringEntry)
|
||||
.map((AssetBundleEntry entry) => entry.archivePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_tools/src/asset.dart';
|
||||
import 'package:flutter_tools/src/devfs.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:test/test.dart';
|
||||
@ -18,6 +19,8 @@ void main() {
|
||||
String basePath;
|
||||
MockDevFSOperations devFSOperations = new MockDevFSOperations();
|
||||
DevFS devFS;
|
||||
AssetBundle assetBundle = new AssetBundle();
|
||||
assetBundle.entries.add(new AssetBundleEntry.fromString('a.txt', ''));
|
||||
group('devfs', () {
|
||||
testUsingContext('create local file system', () async {
|
||||
tempDir = Directory.systemTemp.createTempSync();
|
||||
@ -38,8 +41,6 @@ void main() {
|
||||
testUsingContext('modify existing file on local file system', () async {
|
||||
File file = new File(path.join(basePath, filePath));
|
||||
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
testUsingContext('update dev file system', () async {
|
||||
await devFS.update();
|
||||
expect(devFSOperations.contains('writeFile test bar/foo.txt'), isTrue);
|
||||
});
|
||||
@ -47,11 +48,29 @@ void main() {
|
||||
File file = new File(path.join(basePath, filePath2));
|
||||
await file.parent.create(recursive: true);
|
||||
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
|
||||
});
|
||||
testUsingContext('update dev file system', () async {
|
||||
await devFS.update();
|
||||
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 {
|
||||
await devFS.destroy();
|
||||
});
|
||||
|
||||
@ -75,6 +75,8 @@ class MockDevFSOperations implements DevFSOperations {
|
||||
final List<String> messages = new List<String>();
|
||||
|
||||
bool contains(String match) {
|
||||
print('Checking for `$match` in:');
|
||||
print(messages);
|
||||
bool result = messages.contains(match);
|
||||
messages.clear();
|
||||
return result;
|
||||
@ -96,6 +98,11 @@ class MockDevFSOperations implements DevFSOperations {
|
||||
messages.add('writeFile $fsName ${entry.devicePath}');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> deleteFile(String fsName, DevFSEntry entry) async {
|
||||
messages.add('deleteFile $fsName ${entry.devicePath}');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> writeSource(String fsName,
|
||||
String devicePath,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user