improve startup time
This commit is contained in:
parent
3c7ede15b0
commit
4e10bf596c
@ -3,8 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'src/flx.dart' as flx;
|
||||
|
||||
@ -12,7 +11,7 @@ import 'src/flx.dart' as flx;
|
||||
/// pre-compiled snapshot.
|
||||
Future<int> assembleFlx({
|
||||
Map manifestDescriptor: const {},
|
||||
ArchiveFile snapshotFile: null,
|
||||
File snapshotFile: null,
|
||||
String assetBasePath: flx.defaultAssetBasePath,
|
||||
String materialAssetBasePath: flx.defaultMaterialAssetBasePath,
|
||||
String outputPath: flx.defaultFlxOutputPath,
|
||||
|
@ -5,7 +5,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../android/android_sdk.dart';
|
||||
@ -136,26 +135,16 @@ class AndroidDevice extends Device {
|
||||
}
|
||||
|
||||
String _getSourceSha1(ApplicationPackage app) {
|
||||
var sha1 = new SHA1();
|
||||
var file = new File(app.localPath);
|
||||
sha1.add(file.readAsBytesSync());
|
||||
return CryptoUtils.bytesToHex(sha1.close());
|
||||
File shaFile = new File('${app.localPath}.sha1');
|
||||
return shaFile.existsSync() ? shaFile.readAsStringSync() : '';
|
||||
}
|
||||
|
||||
String get name => modelID;
|
||||
|
||||
@override
|
||||
bool isAppInstalled(ApplicationPackage app) {
|
||||
if (runCheckedSync(adbCommandForDevice(<String>['shell', 'pm', 'path', app.id])) == '') {
|
||||
printTrace('TODO(iansf): move this log to the caller. ${app.name} is not on the device. Installing now...');
|
||||
return false;
|
||||
}
|
||||
if (_getDeviceApkSha1(app) != _getSourceSha1(app)) {
|
||||
printTrace(
|
||||
'TODO(iansf): move this log to the caller. ${app.name} is out of date. Installing now...');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// Just check for the existence of the application SHA.
|
||||
return _getDeviceApkSha1(app) == _getSourceSha1(app);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -179,7 +168,7 @@ class AndroidDevice extends Device {
|
||||
|
||||
if (port == 0) {
|
||||
// Auto-bind to a port. Set up forwarding for that port. Emit a stdout
|
||||
// message similar to the command-line VM, so that tools can parse the output.
|
||||
// message similar to the command-line VM so that tools can parse the output.
|
||||
// "Observatory listening on http://127.0.0.1:52111"
|
||||
port = await findAvailablePort();
|
||||
}
|
||||
@ -258,30 +247,26 @@ class AndroidDevice extends Device {
|
||||
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
|
||||
return false;
|
||||
|
||||
flx.DirectoryResult buildResult = await flx.buildInTempDir(
|
||||
String localBundlePath = await flx.buildFlx(
|
||||
toolchain,
|
||||
mainPath: mainPath
|
||||
);
|
||||
|
||||
printTrace('Starting bundle for $this.');
|
||||
|
||||
try {
|
||||
if (await startBundle(
|
||||
package,
|
||||
buildResult.localBundlePath,
|
||||
checked: checked,
|
||||
traceStartup: platformArgs['trace-startup'],
|
||||
route: route,
|
||||
clearLogs: clearLogs,
|
||||
startPaused: startPaused,
|
||||
debugPort: debugPort
|
||||
)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} finally {
|
||||
buildResult.dispose();
|
||||
if (await startBundle(
|
||||
package,
|
||||
localBundlePath,
|
||||
checked: checked,
|
||||
traceStartup: platformArgs['trace-startup'],
|
||||
route: route,
|
||||
clearLogs: clearLogs,
|
||||
startPaused: startPaused,
|
||||
debugPort: debugPort
|
||||
)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,12 @@ Future<Process> runDetached(List<String> cmd) {
|
||||
|
||||
/// Run cmd and return stdout.
|
||||
/// Throws an error if cmd exits with a non-zero value.
|
||||
String runCheckedSync(List<String> cmd, { String workingDirectory }) {
|
||||
return _runWithLoggingSync(cmd, workingDirectory: workingDirectory, checked: true, noisyErrors: true);
|
||||
String runCheckedSync(List<String> cmd, {
|
||||
String workingDirectory, bool truncateCommand: false
|
||||
}) {
|
||||
return _runWithLoggingSync(
|
||||
cmd, workingDirectory: workingDirectory, checked: true, noisyErrors: true, truncateCommand: truncateCommand
|
||||
);
|
||||
}
|
||||
|
||||
/// Run cmd and return stdout.
|
||||
@ -91,9 +95,13 @@ bool exitsHappy(List<String> cli) {
|
||||
String _runWithLoggingSync(List<String> cmd, {
|
||||
bool checked: false,
|
||||
bool noisyErrors: false,
|
||||
String workingDirectory
|
||||
String workingDirectory,
|
||||
bool truncateCommand: false
|
||||
}) {
|
||||
printTrace(cmd.join(' '));
|
||||
String cmdText = cmd.join(' ');
|
||||
if (truncateCommand && cmdText.length > 160)
|
||||
cmdText = cmdText.substring(0, 160) + '…';
|
||||
printTrace(cmdText);
|
||||
ProcessResult results =
|
||||
Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList(), workingDirectory: workingDirectory);
|
||||
if (results.exitCode != 0) {
|
||||
|
@ -3,6 +3,15 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
String calculateSha(File file) {
|
||||
SHA1 sha1 = new SHA1();
|
||||
sha1.add(file.readAsBytesSync());
|
||||
return CryptoUtils.bytesToHex(sha1.close());
|
||||
}
|
||||
|
||||
/// A class to maintain a list of items, fire events when items are added or
|
||||
/// removed, and calculate a diff of changes when a new list of items is
|
||||
|
@ -13,6 +13,7 @@ import '../artifacts.dart';
|
||||
import '../base/file_system.dart' show ensureDirectoryExists;
|
||||
import '../base/os.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_configuration.dart';
|
||||
import '../device.dart';
|
||||
import '../flx.dart' as flx;
|
||||
@ -296,6 +297,9 @@ int _buildApk(
|
||||
ensureDirectoryExists(finalApk.path);
|
||||
builder.align(unalignedApk, finalApk);
|
||||
|
||||
File apkShaFile = new File('$outputFile.sha1');
|
||||
apkShaFile.writeAsStringSync(calculateSha(finalApk));
|
||||
|
||||
printStatus('Generated APK to ${finalApk.path}.');
|
||||
|
||||
return 0;
|
||||
@ -313,7 +317,7 @@ int _signApk(
|
||||
String keyPassword;
|
||||
|
||||
if (keystoreInfo == null) {
|
||||
printError('Signing the APK using the debug keystore.');
|
||||
printStatus('Warning: signing the APK using the debug keystore.');
|
||||
keystore = components.debugKeystore;
|
||||
keystorePassword = _kDebugKeystorePassword;
|
||||
keyAlias = _kDebugKeystoreKeyAlias;
|
||||
@ -345,13 +349,14 @@ bool _needsRebuild(String apkPath, String manifest) {
|
||||
Iterable<FileStat> dependenciesStat = [
|
||||
manifest,
|
||||
_kFlutterManifestPath,
|
||||
_kPackagesStatusPath
|
||||
_kPackagesStatusPath,
|
||||
'$apkPath.sha1'
|
||||
].map((String path) => FileStat.statSync(path));
|
||||
|
||||
if (apkStat.type == FileSystemEntityType.NOT_FOUND)
|
||||
return true;
|
||||
for (FileStat dep in dependenciesStat) {
|
||||
if (dep.modified.isAfter(apkStat.modified))
|
||||
if (dep.modified == null || dep.modified.isAfter(apkStat.modified))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -381,7 +386,7 @@ Future<int> buildAndroid({
|
||||
}
|
||||
|
||||
if (!force && !_needsRebuild(outputFile, manifest)) {
|
||||
printTrace('APK up to date. Skipping build step.');
|
||||
printTrace('APK up to date; skipping build step.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -408,13 +413,8 @@ Future<int> buildAndroid({
|
||||
String mainPath = findMainDartFile(target);
|
||||
|
||||
// Build the FLX.
|
||||
flx.DirectoryResult buildResult = await flx.buildInTempDir(toolchain, mainPath: mainPath);
|
||||
|
||||
try {
|
||||
return _buildApk(components, buildResult.localBundlePath, keystore, outputFile);
|
||||
} finally {
|
||||
buildResult.dispose();
|
||||
}
|
||||
String localBundlePath = await flx.buildFlx(toolchain, mainPath: mainPath);
|
||||
return _buildApk(components, localBundlePath, keystore, outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flx/bundle.dart';
|
||||
import 'package:flx/signing.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@ -16,6 +14,7 @@ import 'package:yaml/yaml.dart';
|
||||
import 'base/file_system.dart' show ensureDirectoryExists;
|
||||
import 'globals.dart';
|
||||
import 'toolchain.dart';
|
||||
import 'zip.dart';
|
||||
|
||||
const String defaultMainPath = 'lib/main.dart';
|
||||
const String defaultAssetBasePath = '.';
|
||||
@ -140,20 +139,17 @@ dynamic _loadManifest(String manifestPath) {
|
||||
return loadYaml(manifestDescriptor);
|
||||
}
|
||||
|
||||
bool _addAssetFile(Archive archive, _Asset asset) {
|
||||
ZipEntry _createAssetEntry(_Asset asset) {
|
||||
String source = asset.source ?? asset.key;
|
||||
File file = new File('${asset.base}/$source');
|
||||
if (!file.existsSync()) {
|
||||
printError('Cannot find asset "$source" in directory "${path.absolute(asset.base)}".');
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
List<int> content = file.readAsBytesSync();
|
||||
archive.addFile(new ArchiveFile.noCompress(asset.key, content.length, content));
|
||||
return true;
|
||||
return new ZipEntry.fromFile(asset.key, file);
|
||||
}
|
||||
|
||||
ArchiveFile _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
|
||||
String key = 'AssetManifest.json';
|
||||
ZipEntry _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
|
||||
Map<String, List<String>> json = <String, List<String>>{};
|
||||
for (_Asset main in assets.keys) {
|
||||
List<String> variants = <String>[];
|
||||
@ -161,34 +157,25 @@ ArchiveFile _createAssetManifest(Map<_Asset, List<_Asset>> assets) {
|
||||
variants.add(variant.key);
|
||||
json[main.key] = variants;
|
||||
}
|
||||
List<int> content = UTF8.encode(JSON.encode(json));
|
||||
return new ArchiveFile.noCompress(key, content.length, content);
|
||||
return new ZipEntry.fromString('AssetManifest.json', JSON.encode(json));
|
||||
}
|
||||
|
||||
ArchiveFile _createFontManifest(Map manifestDescriptor) {
|
||||
ZipEntry _createFontManifest(Map manifestDescriptor) {
|
||||
if (manifestDescriptor != null && manifestDescriptor.containsKey('fonts')) {
|
||||
List<int> content = UTF8.encode(JSON.encode(manifestDescriptor['fonts']));
|
||||
return new ArchiveFile.noCompress('FontManifest.json', content.length, content);
|
||||
return new ZipEntry.fromString('FontManifest.json', JSON.encode(manifestDescriptor['fonts']));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ArchiveFile _createSnapshotFile(String snapshotPath) {
|
||||
File file = new File(snapshotPath);
|
||||
List<int> content = file.readAsBytesSync();
|
||||
return new ArchiveFile(_kSnapshotKey, content.length, content);
|
||||
}
|
||||
|
||||
/// Build the flx in a temp dir and return `localBundlePath` on success.
|
||||
Future<DirectoryResult> buildInTempDir(
|
||||
/// Build the flx in the build/ directory and return `localBundlePath` on success.
|
||||
Future<String> buildFlx(
|
||||
Toolchain toolchain, {
|
||||
String mainPath: defaultMainPath
|
||||
}) async {
|
||||
int result;
|
||||
Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
|
||||
String localBundlePath = path.join(tempDir.path, 'app.flx');
|
||||
String localSnapshotPath = path.join(tempDir.path, 'snapshot_blob.bin');
|
||||
String localBundlePath = path.join('build', 'app.flx');
|
||||
String localSnapshotPath = path.join('build', 'snapshot_blob.bin');
|
||||
result = await build(
|
||||
toolchain,
|
||||
snapshotPath: localSnapshotPath,
|
||||
@ -196,7 +183,7 @@ Future<DirectoryResult> buildInTempDir(
|
||||
mainPath: mainPath
|
||||
);
|
||||
if (result == 0)
|
||||
return new DirectoryResult(tempDir, localBundlePath);
|
||||
return localBundlePath;
|
||||
else
|
||||
throw result;
|
||||
}
|
||||
@ -227,7 +214,8 @@ Future<int> build(
|
||||
Map manifestDescriptor = _loadManifest(manifestPath);
|
||||
String assetBasePath = path.dirname(path.absolute(manifestPath));
|
||||
|
||||
ArchiveFile snapshotFile = null;
|
||||
File snapshotFile;
|
||||
|
||||
if (!precompiledSnapshot) {
|
||||
ensureDirectoryExists(snapshotPath);
|
||||
|
||||
@ -239,7 +227,7 @@ Future<int> build(
|
||||
return result;
|
||||
}
|
||||
|
||||
snapshotFile = _createSnapshotFile(snapshotPath);
|
||||
snapshotFile = new File(snapshotPath);
|
||||
}
|
||||
|
||||
return assemble(
|
||||
@ -254,7 +242,7 @@ Future<int> build(
|
||||
|
||||
Future<int> assemble({
|
||||
Map manifestDescriptor: const {},
|
||||
ArchiveFile snapshotFile,
|
||||
File snapshotFile,
|
||||
String assetBasePath: defaultAssetBasePath,
|
||||
String materialAssetBasePath: defaultMaterialAssetBasePath,
|
||||
String outputPath: defaultFlxOutputPath,
|
||||
@ -265,25 +253,32 @@ Future<int> assemble({
|
||||
Map<_Asset, List<_Asset>> assets = _parseAssets(manifestDescriptor, assetBasePath);
|
||||
assets.addAll(_parseMaterialAssets(manifestDescriptor, materialAssetBasePath));
|
||||
|
||||
Archive archive = new Archive();
|
||||
ZipBuilder zipBuilder = new ZipBuilder();
|
||||
|
||||
if (snapshotFile != null)
|
||||
archive.addFile(snapshotFile);
|
||||
zipBuilder.addEntry(new ZipEntry.fromFile(_kSnapshotKey, snapshotFile));
|
||||
|
||||
for (_Asset asset in assets.keys) {
|
||||
if (!_addAssetFile(archive, asset))
|
||||
ZipEntry assetEntry = _createAssetEntry(asset);
|
||||
if (assetEntry == null)
|
||||
return 1;
|
||||
else
|
||||
zipBuilder.addEntry(assetEntry);
|
||||
|
||||
for (_Asset variant in assets[asset]) {
|
||||
if (!_addAssetFile(archive, variant))
|
||||
ZipEntry variantEntry = _createAssetEntry(variant);
|
||||
if (variantEntry == null)
|
||||
return 1;
|
||||
else
|
||||
zipBuilder.addEntry(variantEntry);
|
||||
}
|
||||
}
|
||||
|
||||
archive.addFile(_createAssetManifest(assets));
|
||||
zipBuilder.addEntry(_createAssetManifest(assets));
|
||||
|
||||
ArchiveFile fontManifest = _createFontManifest(manifestDescriptor);
|
||||
ZipEntry fontManifest = _createFontManifest(manifestDescriptor);
|
||||
if (fontManifest != null)
|
||||
archive.addFile(fontManifest);
|
||||
zipBuilder.addEntry(fontManifest);
|
||||
|
||||
AsymmetricKeyPair keyPair = keyPairFromPrivateKeyFileSync(privateKeyPath);
|
||||
printTrace('KeyPair from $privateKeyPath: $keyPair.');
|
||||
@ -293,8 +288,10 @@ Future<int> assemble({
|
||||
CipherParameters.get().seedRandom();
|
||||
}
|
||||
|
||||
printTrace('Encoding zip file.');
|
||||
Uint8List zipBytes = new Uint8List.fromList(new ZipEncoder().encode(archive));
|
||||
File zipFile = new File(outputPath.substring(0, outputPath.length - 4) + '.zip');
|
||||
printTrace('Encoding zip file to ${zipFile.path}');
|
||||
zipBuilder.createZip(zipFile, new Directory('build/flx'));
|
||||
List<int> zipBytes = zipFile.readAsBytesSync();
|
||||
|
||||
ensureDirectoryExists(outputPath);
|
||||
|
||||
@ -307,7 +304,7 @@ Future<int> assemble({
|
||||
);
|
||||
bundle.writeSync();
|
||||
|
||||
printTrace('Built and signed flx at $outputPath.');
|
||||
printTrace('Built $outputPath.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
117
packages/flutter_tools/lib/src/zip.dart
Normal file
117
packages/flutter_tools/lib/src/zip.dart
Normal file
@ -0,0 +1,117 @@
|
||||
// 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:io';
|
||||
import 'dart:convert' show UTF8;
|
||||
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'base/process.dart';
|
||||
|
||||
abstract class ZipBuilder {
|
||||
factory ZipBuilder() {
|
||||
if (exitsHappy(<String>['which', 'zip'])) {
|
||||
return new _ZipToolBuilder();
|
||||
} else {
|
||||
return new _ArchiveZipBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
ZipBuilder._();
|
||||
|
||||
List<ZipEntry> entries = <ZipEntry>[];
|
||||
|
||||
void addEntry(ZipEntry 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._();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
List<int> zipData = new ZipEncoder().encode(archive);
|
||||
outFile.writeAsBytesSync(zipData);
|
||||
}
|
||||
}
|
||||
|
||||
class _ZipToolBuilder extends ZipBuilder {
|
||||
_ZipToolBuilder() : super._();
|
||||
|
||||
void createZip(File outFile, Directory zipBuildDir) {
|
||||
if (outFile.existsSync())
|
||||
outFile.deleteSync();
|
||||
|
||||
if (zipBuildDir.existsSync())
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
runCheckedSync(
|
||||
<String>['zip', '-q', outFile.absolute.path]..addAll(_getCompressedNames()),
|
||||
workingDirectory: zipBuildDir.path,
|
||||
truncateCommand: true
|
||||
);
|
||||
runCheckedSync(
|
||||
<String>['zip', '-q', '-0', outFile.absolute.path]..addAll(_getStoredNames()),
|
||||
workingDirectory: zipBuildDir.path,
|
||||
truncateCommand: true
|
||||
);
|
||||
}
|
||||
|
||||
Iterable<String> _getCompressedNames() {
|
||||
return entries
|
||||
.where((ZipEntry entry) => !entry.isStringEntry)
|
||||
.map((ZipEntry entry) => entry.archivePath);
|
||||
}
|
||||
|
||||
Iterable<String> _getStoredNames() {
|
||||
return entries
|
||||
.where((ZipEntry entry) => entry.isStringEntry)
|
||||
.map((ZipEntry entry) => entry.archivePath);
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ class Bundle {
|
||||
Bundle.fromContent({
|
||||
this.path,
|
||||
this.manifest,
|
||||
contentBytes,
|
||||
List<int> contentBytes,
|
||||
AsymmetricKeyPair keyPair
|
||||
}) : _contentBytes = contentBytes {
|
||||
assert(path != null);
|
||||
|
Loading…
x
Reference in New Issue
Block a user