Commit 28682568 authored by Devon Carew's avatar Devon Carew

Merge pull request #2245 from devoncarew/improve_startup_time

improve startup time
parents 3c7ede15 4e10bf59
......@@ -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;
}
// 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);
......
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