Commit 6a63af40 authored by John McCutchan's avatar John McCutchan Committed by GitHub

Rebuild Android apk when Dart source is modified (#7345)

- [x] Wire up dependency checker and plumb flags down to the right place

Fixes #7014
parent 68425979
......@@ -269,7 +269,8 @@ class AndroidDevice extends Device {
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false,
}) async {
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
return new LaunchResult.failed();
......@@ -281,7 +282,8 @@ class AndroidDevice extends Device {
printTrace('Building APK');
await buildApk(platform,
target: mainPath,
buildMode: debuggingOptions.buildMode
buildMode: debuggingOptions.buildMode,
applicationNeedsRebuild: applicationNeedsRebuild
);
}
......
......@@ -27,6 +27,8 @@ abstract class ApplicationPackage {
String get displayName => name;
String get packagePath => null;
@override
String toString() => displayName;
}
......@@ -119,6 +121,9 @@ class AndroidApk extends ApplicationPackage {
);
}
@override
String get packagePath => apkPath;
@override
String get name => path.basename(apkPath);
}
......
......@@ -483,7 +483,8 @@ Future<Null> buildAndroid(
String target,
String flxPath,
String aotPath,
ApkKeystoreInfo keystore
ApkKeystoreInfo keystore,
bool applicationNeedsRebuild: false
}) async {
outputFile ??= _defaultOutputPath;
......@@ -508,12 +509,14 @@ Future<Null> buildAndroid(
}
}
final bool needRebuild =
applicationNeedsRebuild ||
_needsRebuild(outputFile, manifest, platform, buildMode, extraFiles);
// In debug (JIT) mode, the snapshot lives in the FLX, and we can skip the APK
// rebuild if none of the resources in the APK are stale.
// In AOT modes, the snapshot lives in the APK, so the APK must be rebuilt.
if (!isAotBuildMode(buildMode) &&
!force &&
!_needsRebuild(outputFile, manifest, platform, buildMode, extraFiles)) {
if (!isAotBuildMode(buildMode) && !force && !needRebuild) {
printTrace('APK up to date; skipping build step.');
return;
}
......@@ -608,7 +611,8 @@ Future<Null> buildAndroidWithGradle(
Future<Null> buildApk(
TargetPlatform platform, {
String target,
BuildMode buildMode: BuildMode.debug
BuildMode buildMode: BuildMode.debug,
bool applicationNeedsRebuild: false,
}) async {
if (isProjectUsingGradle()) {
return await buildAndroidWithGradle(
......@@ -625,7 +629,8 @@ Future<Null> buildApk(
platform,
buildMode,
force: false,
target: target
target: target,
applicationNeedsRebuild: applicationNeedsRebuild,
);
}
}
......
......@@ -61,6 +61,7 @@ class DependencyChecker {
return true;
}
}
printTrace('DependencyChecker: nothing is modified after $threshold.');
return false;
}
}
......@@ -205,7 +205,8 @@ abstract class Device {
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false
});
/// Does this device implement support for hot reloading / restarting?
......
......@@ -9,13 +9,12 @@ import 'package:path/path.dart' as path;
import 'package:stack_trace/stack_trace.dart';
import 'application_package.dart';
import 'asset.dart';
import 'base/context.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'dart/package_map.dart';
import 'dart/dependencies.dart';
import 'devfs.dart';
import 'device.dart';
......@@ -50,26 +49,16 @@ class HotRunner extends ResidentRunner {
}) : super(device,
target: target,
debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI) {
_projectRootPath = projectRootPath ?? fs.currentDirectory.path;
_packagesFilePath =
packagesFilePath ?? path.absolute(PackageMap.globalPackagesPath);
if (projectAssets != null)
_bundle = new AssetBundle.fixed(_projectRootPath, projectAssets);
else
_bundle = new AssetBundle();
}
usesTerminalUI: usesTerminalUI,
projectRootPath: projectRootPath,
packagesFilePath: packagesFilePath,
projectAssets: projectAssets);
ApplicationPackage _package;
String _mainPath;
String _projectRootPath;
String _packagesFilePath;
final String applicationBinary;
bool get prebuiltMode => applicationBinary != null;
Set<String> _dartDependencies;
Uri _observatoryUri;
AssetBundle _bundle;
AssetBundle get bundle => _bundle;
final bool benchmarkMode;
final Map<String, int> benchmarkData = new Map<String, int>();
// The initial launch is from a snapshot.
......@@ -83,7 +72,6 @@ class HotRunner extends ResidentRunner {
bool shouldBuild: true
}) {
// Don't let uncaught errors kill the process.
assert(shouldBuild == !prebuiltMode);
return Chain.capture(() {
return _run(
connectionInfoCompleter: connectionInfoCompleter,
......@@ -107,7 +95,7 @@ class HotRunner extends ResidentRunner {
}
DartDependencySetBuilder dartDependencySetBuilder =
new DartDependencySetBuilder(
_mainPath, _projectRootPath, _packagesFilePath);
mainPath, projectRootPath, packagesFilePath);
try {
Set<String> dependencies = dartDependencySetBuilder.build();
_dartDependencies = new Set<String>();
......@@ -135,18 +123,17 @@ class HotRunner extends ResidentRunner {
String route,
bool shouldBuild: true
}) async {
_mainPath = findMainDartFile(target);
if (!fs.isFileSync(_mainPath)) {
String message = 'Tried to run $_mainPath, but that file does not exist.';
if (!fs.isFileSync(mainPath)) {
String message = 'Tried to run $mainPath, but that file does not exist.';
if (target == null)
message += '\nConsider using the -t option to specify the Dart file to start.';
printError(message);
return 1;
}
_package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary);
package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary);
if (_package == null) {
if (package == null) {
String message = 'No application found for ${device.platform}.';
String hint = getMissingPackageHintForPlatform(device.platform);
if (hint != null)
......@@ -163,20 +150,21 @@ class HotRunner extends ResidentRunner {
Map<String, dynamic> platformArgs = new Map<String, dynamic>();
await startEchoingDeviceLog(_package);
await startEchoingDeviceLog(package);
String modeName = getModeName(debuggingOptions.buildMode);
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name} in $modeName mode...');
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
// Start the application.
Future<LaunchResult> futureResult = device.startApp(
_package,
package,
debuggingOptions.buildMode,
mainPath: _mainPath,
mainPath: mainPath,
debuggingOptions: debuggingOptions,
platformArgs: platformArgs,
route: route,
prebuiltApplication: prebuiltMode
prebuiltApplication: prebuiltMode,
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies()
);
LaunchResult result = await futureResult;
......@@ -264,11 +252,11 @@ class HotRunner extends ResidentRunner {
DevFS _devFS;
Future<Uri> _initDevFS() {
String fsName = path.basename(_projectRootPath);
String fsName = path.basename(projectRootPath);
_devFS = new DevFS(vmService,
fsName,
fs.directory(_projectRootPath),
packagesFilePath: _packagesFilePath);
fs.directory(projectRootPath),
packagesFilePath: packagesFilePath);
return _devFS.create();
}
......@@ -277,16 +265,16 @@ class HotRunner extends ResidentRunner {
// Did not update DevFS because of a Dart source error.
return false;
}
final bool rebuildBundle = bundle.needsBuild();
final bool rebuildBundle = assetBundle.needsBuild();
if (rebuildBundle) {
printTrace('Updating assets');
int result = await bundle.build();
int result = await assetBundle.build();
if (result != 0)
return false;
}
Status devFSStatus = logger.startProgress('Syncing files to device...');
await _devFS.update(progressReporter: progressReporter,
bundle: bundle,
bundle: assetBundle,
bundleDirty: rebuildBundle,
fileFilter: _dartDependencies);
devFSStatus.stop();
......@@ -329,7 +317,7 @@ class HotRunner extends ResidentRunner {
Future<Null> _launchFromDevFS(ApplicationPackage package,
String mainScript) async {
String entryPath = path.relative(mainScript, from: _projectRootPath);
String entryPath = path.relative(mainScript, from: projectRootPath);
String deviceEntryPath =
_devFS.baseUri.resolve(entryPath).toFilePath();
String devicePackagesPath =
......@@ -347,7 +335,7 @@ class HotRunner extends ResidentRunner {
bool updatedDevFS = await _updateDevFS();
if (!updatedDevFS)
return new OperationResult(1, 'Dart Source Error');
await _launchFromDevFS(_package, _mainPath);
await _launchFromDevFS(package, mainPath);
restartTimer.stop();
printTrace('Restart performed in '
'${getElapsedAsMilliseconds(restartTimer.elapsed)}.');
......@@ -440,7 +428,7 @@ class HotRunner extends ResidentRunner {
return new OperationResult(1, 'Dart Source Error');
String reloadMessage;
try {
String entryPath = path.relative(_mainPath, from: _projectRootPath);
String entryPath = path.relative(mainPath, from: projectRootPath);
String deviceEntryPath =
_devFS.baseUri.resolve(entryPath).toFilePath();
String devicePackagesPath =
......
......@@ -186,7 +186,8 @@ class IOSDevice extends Device {
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false,
}) async {
if (!prebuiltApplication) {
// TODO(chinmaygarde): Use checked, mainPath, route.
......
......@@ -416,7 +416,8 @@ class IOSSimulator extends Device {
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication: false
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false,
}) async {
if (!prebuiltApplication) {
printTrace('Building ${app.name} for $id.');
......
......@@ -7,11 +7,19 @@ import 'dart:async';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'application_package.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'asset.dart';
import 'base/logger.dart';
import 'build_info.dart';
import 'dart/dependencies.dart';
import 'dart/package_map.dart';
import 'dependency_checker.dart';
import 'device.dart';
import 'globals.dart';
import 'vmservice.dart';
......@@ -21,14 +29,35 @@ abstract class ResidentRunner {
ResidentRunner(this.device, {
this.target,
this.debuggingOptions,
this.usesTerminalUI: true
});
this.usesTerminalUI: true,
String projectRootPath,
String packagesFilePath,
String projectAssets,
}) {
_mainPath = findMainDartFile(target);
_projectRootPath = projectRootPath ?? fs.currentDirectory;
_packagesFilePath =
packagesFilePath ?? path.absolute(PackageMap.globalPackagesPath);
if (projectAssets != null)
_assetBundle = new AssetBundle.fixed(_projectRootPath, projectAssets);
else
_assetBundle = new AssetBundle();
}
final Device device;
final String target;
final DebuggingOptions debuggingOptions;
final bool usesTerminalUI;
final Completer<int> _finished = new Completer<int>();
String _packagesFilePath;
String get packagesFilePath => _packagesFilePath;
String _projectRootPath;
String get projectRootPath => _projectRootPath;
String _mainPath;
String get mainPath => _mainPath;
AssetBundle _assetBundle;
AssetBundle get assetBundle => _assetBundle;
ApplicationPackage package;
bool get isRunningDebug => debuggingOptions.buildMode == BuildMode.debug;
bool get isRunningProfile => debuggingOptions.buildMode == BuildMode.profile;
......@@ -233,6 +262,27 @@ abstract class ResidentRunner {
return exitCode;
}
bool hasDirtyDependencies() {
DartDependencySetBuilder dartDependencySetBuilder =
new DartDependencySetBuilder(
mainPath, projectRootPath, packagesFilePath);
DependencyChecker dependencyChecker =
new DependencyChecker(dartDependencySetBuilder, assetBundle);
String path = package.packagePath;
if (path == null) {
return true;
}
final FileStat stat = fs.file(path).statSync();
if (stat.type != FileSystemEntityType.FILE) {
return true;
}
if (!fs.file(path).existsSync()) {
return true;
}
final DateTime lastBuildTime = stat.modified;
return dependencyChecker.check(lastBuildTime);
}
Future<Null> preStop() async { }
Future<Null> stopApp() async {
......
......@@ -29,8 +29,6 @@ class RunAndStayResident extends ResidentRunner {
debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI);
ApplicationPackage _package;
String _mainPath;
LaunchResult _result;
final bool traceStartup;
final String applicationBinary;
......@@ -46,7 +44,6 @@ class RunAndStayResident extends ResidentRunner {
}) {
// Don't let uncaught errors kill the process.
return Chain.capture(() {
assert(shouldBuild == !prebuiltMode);
return _run(
traceStartup: traceStartup,
connectionInfoCompleter: connectionInfoCompleter,
......@@ -67,9 +64,8 @@ class RunAndStayResident extends ResidentRunner {
bool shouldBuild: true
}) async {
if (!prebuiltMode) {
_mainPath = findMainDartFile(target);
if (!fs.isFileSync(_mainPath)) {
String message = 'Tried to run $_mainPath, but that file does not exist.';
if (!fs.isFileSync(mainPath)) {
String message = 'Tried to run $mainPath, but that file does not exist.';
if (target == null)
message += '\nConsider using the -t option to specify the Dart file to start.';
printError(message);
......@@ -77,9 +73,9 @@ class RunAndStayResident extends ResidentRunner {
}
}
_package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary);
package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary);
if (_package == null) {
if (package == null) {
String message = 'No application found for ${device.platform}.';
String hint = getMissingPackageHintForPlatform(device.platform);
if (hint != null)
......@@ -94,24 +90,25 @@ class RunAndStayResident extends ResidentRunner {
if (traceStartup != null)
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
await startEchoingDeviceLog(_package);
await startEchoingDeviceLog(package);
String modeName = getModeName(debuggingOptions.buildMode);
if (_mainPath == null) {
if (mainPath == null) {
assert(prebuiltMode);
printStatus('Launching ${_package.displayName} on ${device.name} in $modeName mode...');
printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
} else {
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name} in $modeName mode...');
printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
}
_result = await device.startApp(
_package,
package,
debuggingOptions.buildMode,
mainPath: _mainPath,
mainPath: mainPath,
debuggingOptions: debuggingOptions,
platformArgs: platformArgs,
route: route,
prebuiltApplication: prebuiltMode
prebuiltApplication: prebuiltMode,
applicationNeedsRebuild: shouldBuild || hasDirtyDependencies()
);
if (!_result.started) {
......@@ -195,6 +192,6 @@ class RunAndStayResident extends ResidentRunner {
Future<Null> preStop() async {
// If we're running in release mode, stop the app using the device logic.
if (vmService == null)
await device.stopApp(_package);
await device.stopApp(package);
}
}
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