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